From 022d326ce05667ddfc111aad8d0c511c2c8563e1 Mon Sep 17 00:00:00 2001 From: alfred Date: Fri, 27 Apr 2018 15:19:18 +0200 Subject: [PATCH] Add jsxgraphsrc.js to git. This file can be used for autocompletion in WebStorm --- distrib/jsxgraphsrc.js | 59922 +++++++++++++++++++++++++++++++++++++++ src/themes/gui.js | 2 +- 2 files changed, 59923 insertions(+), 1 deletion(-) create mode 100644 distrib/jsxgraphsrc.js diff --git a/distrib/jsxgraphsrc.js b/distrib/jsxgraphsrc.js new file mode 100644 index 000000000..a8ad3ce1f --- /dev/null +++ b/distrib/jsxgraphsrc.js @@ -0,0 +1,59922 @@ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], 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-2017 + 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 + , , + and . + + */ + + +/*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 + */ + 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 extension object to object. + * @param {Object} object + * @param {Object} extension + * @param {Boolean} [onlyOwn=false] Only consider properties that belong to extension itself, not any inherited properties. + * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see 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]; + } + } + }; + + 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.JSXGraph.registerElement}. + * @type Object + */ + elements: {}, + + /** + * This registers a new construction element to JSXGraph for the construction via the {@link JXG.Board.create} + * interface. + * @param {String} element The elements name. This is case-insensitive, existing elements with the same name + * will be overwritten. + * @param {Function} creator A reference to a function taking three parameters: First the board, the element is + * to be created on, a parent element array, and an attributes object. See {@link JXG.createPoint} or any other + * JXG.create... function for an example. + */ + registerElement: function (element, creator) { + element = element.toLowerCase(); + this.elements[element] = creator; + }, + + /** + * Register a file reader. + * @param {function} reader A file reader. This object has to provide two methods: prepareString() + * and read(). + * @param {Array} ext + */ + registerReader: function (reader, ext) { + var i, e; + + for (i = 0; i < ext.length; i++) { + e = ext[i].toLowerCase(); + + if (typeof this.readers[e] !== 'function') { + this.readers[e] = reader; + } + } + }, + + /** + * Creates a shortcut to a method, e.g. {@link JXG.Board#createElement} is a shortcut to {@link JXG.Board#create}. + * Sometimes the target is undefined by the time you want to define the shortcut so we need this little helper. + * @param {Object} object The object the method we want to create a shortcut for belongs to. + * @param {String} fun The method we want to create a shortcut for. + * @returns {Function} A function that calls the given method. + */ + shortcut: function (object, fun) { + return function () { + return object[fun].apply(this, arguments); + }; + }, + + /** + * s may be a string containing the name or id of an element or even a reference + * to the element itself. This function returns a reference to the element. Search order: id, name. + * @param {JXG.Board} board Reference to the board the element belongs to. + * @param {String} s String or reference to a JSXGraph element. + * @returns {Object} Reference to the object given in parameter object + * @deprecated Use {@link JXG.Board#select} + */ + getRef: function (board, s) { + jxg.deprecated('JXG.getRef()', 'Board.select()'); + return board.select(s); + }, + + /** + * This is just a shortcut to {@link JXG.getRef}. + * @deprecated Use {@link JXG.Board#select}. + */ + getReference: function (board, s) { + jxg.deprecated('JXG.getReference()', 'Board.select()'); + return board.select(s); + }, + + /** + * s may be the string containing the id of an HTML tag that hosts a JSXGraph board. + * This function returns the reference to the board. + * @param {String} s String of an HTML tag that hosts a JSXGraph board + * @returns {Object} Reference to the board or null. + */ + getBoardByContainerId: function(s) { + var b; + for (b in JXG.boards) { + if (JXG.boards.hasOwnProperty(b) && + JXG.boards[b].container === s) { + return JXG.boards[b]; + } + } + + return null; + }, + + /** + * This method issues a warning to the developer that the given function is deprecated + * and, if available, offers an alternative to the deprecated function. + * @param {String} what Describes the function that is deprecated + * @param {String} [replacement] The replacement that should be used instead. + */ + deprecated: function (what, replacement) { + var warning = what + ' is deprecated.'; + + if (replacement) { + warning += ' Please use ' + replacement + ' instead.'; + } + + jxg.warn(warning); + }, + + /** + * Outputs a warning via console.warn(), if available. If console.warn() is + * unavailable this function will look for an HTML element with the id 'warning' + * and append the warning to this element's 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 + '
'; + } + }, + + /** + * 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 + "
"; + } + } + }, + + /** + * 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')); + } + }, + + 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-2017 + 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 + and . + */ + + +/*global JXG: true, define: true*/ +/*jslint nomen: true, plusplus: true*/ + +/* depends: + jxg + */ + +define('base/constants',['jxg'], function (JXG) { + + "use strict"; + + var major = 0, + minor = 99, + patch = 6, + add = false, //false, // 'dev', + version = major + '.' + minor + '.' + patch + (add ? '-' + add : ''), + constants; + + constants = /** @lends JXG */ { + // copyright, version, ... + + /** + * Represents the currently used JSXGraph version. + * @type {String} + */ + version: version, + + /** + * The small gray version indicator in the top left corner of every JSXGraph board (if + * showCopyright is not set to false on board creation). + * @type String + */ + licenseText: 'JSXGraph v' + version + ' Copyright (C) see http://jsxgraph.org', + + // coords + COORDS_BY_USER: 0x0001, + 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 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, + GENTYPE_REFLECTION: 4, + GENTYPE_MIRRORPOINT: 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, + 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, + // 38 ... 39 // unused ... + GENTYPE_DELETE: 41, + GENTYPE_COPY: 42, + GENTYPE_MIRROR: 43, + GENTYPE_ROTATE: 44, + GENTYPE_ABLATION: 45, + GENTYPE_MIGRATE: 46, + GENTYPE_VECTORCOPY: 47, +// 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 + }; + + JXG.extend(JXG, constants); + + return constants; +}); + +/* + Copyright 2008-2017 + 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 + and . + */ + + +/*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; + + if (this.isArray(v)) { + return true; + } + if (this.isFunction(v)) { + val = v(); + if (this.isArray(val) && val.length > 1) { + return true; + } + } + v = board.select(v); + return this.isPoint(v); + }, + + /** + * 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. + * @returns {Boolean} True, if v is neither undefined nor null. + */ + exists: (function (undef) { + return function (v) { + return !(v === undef || v === null); + }; + }()), + + /** + * Handle default parameters. + * @param v Given value + * @param d Default value + * @returns d, if v is undefined or null. + */ + def: function (v, d) { + if (this.exists(v)) { + return v; + } + + return d; + }, + + /** + * Converts a string containing either true or false into a boolean value. + * @param {String} s String containing either true or false. + * @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 GEONEXT 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 param. + * @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 GEONEXT 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 GEONEXT string given as term. + * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONEXT 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 + *
    + *
  • {@link JXG.Point} objects + *
  • {@link JXG.Element#name} of {@link JXG.Point} objects + *
  • {@link JXG.Element#id} of {@link JXG.Point} objects + *
  • Coordinates of points given as array of numbers of length two or three, e.g. [2, 3]. + *
  • 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; }] + *
  • Function returning coordinates, e.g. function() { return [2, 3]; } + *
+ * 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)); + } else if (this.isFunction(parents[i])) { + val = parents[i](); + if (this.isArray(val) && (val.length > 1)) { + points.push(board.create('point', [parents[i]], attr)); + } + } 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 val 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 val is a function, it is evaluated and the result is returned. Otherwise val 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 + * -1 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 val 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} true, 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 n after p 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 (typeof 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() + * + * @param {Number} num Number tp be rounded + * @param {Number} precision Decimal digits + * @return {String} Rounded number is returned as string + */ + toFixed: function(num, precision) { + return this._round10(num, -precision).toFixed(precision); + }, + + /** + * Truncate a number val automatically. + * @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 obj. + */ + 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 () {}; + + 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) { + 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) { + 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); + } + + // 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) { + 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: '', + 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 & by &amp;, > by &gt;, and < by &lt;. + * @param {String} str + * @returns {String} + */ + escapeHTML: function (str) { + return str.replace(/&/g, '&').replace(//g, '>'); + }, + + /** + * Eliminates all substrings enclosed by < and > and replaces all occurences of + * &amp; by &, &gt; by >, and &lt; by <. + * @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(/&/g, '&').replace(/</g, '<').replace(/>/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; + }, + + /** + * 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 () {}, function (id) { return id; }); + } + + if (str) { + str = str.replace(//g, '>'); + } + + return str; + }, + + /** + * If s 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-2017 + 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 + and . + */ + + +/*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.extend(JXG, /** @lends JXG */ { + /** + * Determines the property that stores the relevant information in the event object. + * @type {String} + * @default 'touches' + */ + touchProperty: 'touches', + + /** + * A document/window environment is available. + * @type Boolean + * @default false + */ + isBrowser: typeof window === 'object' && typeof document === 'object', + + /** + * 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.navigator.msPointerEnabled || window.navigator.pointerEnabled); + }, + + /** + * 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 undef, div, all, + v = 3; + + if (typeof document !== 'object') { + return 0; + } + + div = document.createElement('div'); + all = div.getElementsByTagName('i'); + + do { + div.innerHTML = ''; + } while (all[0]); + + return v > 4 ? v : undef; + + }()), + + /** + * 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('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, window.event will be used. + * @param {Number} [index] If e 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 undefined 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 0 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, tr, + 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); + } + }); + + return JXG; +}); + +/* + Copyright 2008-2013 + 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 + and . + */ + + +/*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-2013 + 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 + and . + */ + + +/*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 on, off, triggerEventHandlers via the + * borrows tag as methods to their documentation: + *
@borrows JXG.EventEmitter#on as this.on
+ * @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-2017 + 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 + and . + */ + + +/*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. + * They 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 a mod m + */ + 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 n times m-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 n times m-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 n with all coefficients equal to 0 except a_(i,i), i out of (1, ..., n), if m is undefined or not a number + * or a n times m-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 + *
    + *
  • the intersection of two lines
  • + *
  • the line through two points
  • + *
+ * @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]]; + }, + + /** + * 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. + * @param {Number} x The number the cosine hyperbolicus will be calculated of. + * @returns {Number} Cosine hyperbolicus of the given value. + */ + cosh: function (x) { + return (Math.exp(x) + Math.exp(-x)) * 0.5; + }, + + /** + * Sine hyperbolicus of x. + * @param {Number} x The number the sine hyperbolicus will be calculated of. + * @returns {Number} Sine hyperbolicus of the given value. + */ + sinh: function (x) { + return (Math.exp(x) - Math.exp(-x)) * 0.5; + }, + + /** + * 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; + } + + if (Math.floor(exponent) === exponent) { + // a is an integer + return Math.pow(base, exponent); + } + + // a is not an integer + if (base > 0) { + return Math.exp(exponent * Math.log(Math.abs(base))); + } + + return NaN; + }, + + /** + * 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. + * @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 http://rosettacode.org/wiki/Greatest_common_divisor#JavaScript + * + * @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; } + } + }, + + /** + * 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; + } + }; + + return JXG.Math; +}); + +/* + Copyright 2008-2013 + 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 + and . + */ + + +/*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 COORDS_BY_SCREEN and COORDS_BY_USER. + * @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, true, 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 JXG.COORDS_BY_USER and JXG.COORDS_BY_SCREEN. + * @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 COORDS_BY_USER and COORDS_BY_SCREEN. + * @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, + 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 srcCoords or usrCoords + * Uses slice() in case of standard arrays and set() in case of + * typed arrays. + * @private + * @param {String} obj Either 'srcCoords' 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); + }, + + /** + * 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-2013 + 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 + and . + */ + + +/*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-2013 + 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 + and . + */ + + +/*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"; + + var + /** + * Instantiate a new quad tree. + * @param {Array} bbox Bounding box of the new quad (sub)tree. + * @constructor + */ + 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. + * @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. + * @type {JXG.Quadtree} + */ + this.northWest = null; + + /** + * In a subdivided quad tree this represents the top right subtree. + * @type {JXG.Quadtree} + */ + this.northEast = null; + + /** + * In a subdivided quad tree this represents the bottom right subtree. + * @type {JXG.Quadtree} + */ + this.southEast = null; + + /** + * In a subdivided quad tree this represents the bottom left subtree. + * @type {JXG.Quadtree} + */ + this.southWest = null; + }; + + Type.extend(Quadtree.prototype, /** @lends JXG.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 Quadtree([this.xlb, this.yub, mx, my]); + this.northEast = new Quadtree([mx, this.yub, this.xub, my]); + this.southEast = new Quadtree([this.xlb, my, mx, this.ylb]); + this.southWest = new 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. + * @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. + * @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); + } + }); + + Mat.Quadtree = Quadtree; + + return Quadtree; +}); + +/* + Copyright 2008-2017 + 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 + and . + */ + +/*global JXG: true, define: true*/ +/*jslint nomen: true, plusplus: true*/ + +/* 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', 'math/math'], function (JXG, Type, 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 < 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, + 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 f 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 f 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 f 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 f 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, abserr, + 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, resObj, + 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. + * @param {function} f We search for a solution of f(x)=0. + * @param {Number} x initial guess for the root, i.e. starting 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 + */ + root: function (f, x, context) { + return this.fzero(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)) and two numbers x1 and x2 defining the curve's domain. x1 always equals zero. + * @memberof JXG.Math.Numerics + */ + 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, '', 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. + * @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 + */ + lagrangePolynomial: function (p) { + var w = [], + /** @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 (j = 0; j < len; j++) { + 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; + }; + + fct.getTerm = function () { + return ''; + }; + + return fct; + }, + + /** + * 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 importeant, 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](); + } else 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]; + + 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 dataY 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, N = [], + _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, n, 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, + 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, n, 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, suspendUpdate) { + var h = 0.00001, + h2 = (h * 2.0); + + // Experiments with Richardsons rule + /* + var phi = (f(x + h, suspendUpdate) - f(x - h, suspendUpdate)) / h2; + var phi2; + h *= 0.5; + h2 *= 0.5; + phi2 = (f(x + h, suspendUpdate) - f(x - h, suspendUpdate)) / h2; + + return phi2 + (phi2 - phi) / 3.0; + */ + return (f(x + h, suspendUpdate) - f(x - h, suspendUpdate)) / h2; + }; + } + + return function (x, suspendUpdate) { + var h = 0.00001, + h2 = (h * 2.0); + + return (f.apply(obj, [x + h, suspendUpdate]) - f.apply(obj, [x - h, suspendUpdate])) / 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 + *
+         * {
+         *     s: <Number>,
+         *     A: <matrix>,
+         *     b: <Array>,
+         *     c: <Array>
+         * }
+         * 
+ * 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
dx/dt = f(t, x(t)).
So f has to take two parameters, a number t and a + * vector x, and has to return a vector of the same dimension as x 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<data.length; i++) { + * dataX[i] = i*h; + * dataY[i] = data[i][0]; + * } + * var g = board.create('curve', [dataX, dataY], {strokeWidth:'2px'}); + *
+ *
+         * @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}
+         * @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,
+
+        /**
+         *
+         * 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:
+         *  G.Forsythe, M.Malcolm, C.Moler, Computer methods for mathematical
+         *  computations. M., Mir, 1980, p.180 of the Russian edition
+         *
+         * 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.
+         * @memberof JXG.Math.Numerics
+         */
+        fzero: function (f, x0, object) {
+            var a, b, c,
+                fa, fb, fc,
+                aa, blist, i, len, u, fu,
+                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 {
+                a = x0;
+                fa = f.call(object, a);
+                nfev += 1;
+
+                // Try to get b.
+                if (a === 0) {
+                    aa = 1;
+                } else {
+                    aa = a;
+                }
+
+                blist = [0.9 * aa, 1.1 * aa, aa - 1, aa + 1, 0.5 * aa, 1.5 * aa, -aa, 2 * aa, -10 * aa, 10 * 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;
+                }
+            }
+
+            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) {
+                    if (new_step > 0) {
+                        new_step = tol_act;
+                    } else {
+                        new_step = -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 minimum of an univariate function f.
+         * 

+ * 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 x is smaller than eps it is considered to be equal 0. + * @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 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, + dist = 0, + f = i; + + if (j - i < 2) { + return [-1.0, 0]; + } + + ci = pts[i].scrCoords; + cj = pts[j].scrCoords; + + if (isNaN(ci[1] + ci[2])) { + return [NaN, i]; + } + if (isNaN(cj[1] + cj[2])) { + return [NaN, j]; + } + + for (k = i + 1; k < j; k++) { + ck = pts[k].scrCoords; + if (isNaN(ck[1] + 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]; + 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 x is smaller than eps it is considered to be equal 0. + * @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; + + // Search for the left most point woithout NaN coordinates + i = 0; + while (i < len && isNaN(pts[i].scrCoords[1] + pts[i].scrCoords[2])) { + i += 1; + } + // Search for the right most point woithout NaN coordinates + k = len - 1; + while (k > i && isNaN(pts[k].scrCoords[1] + pts[k].scrCoords[2])) { + k -= 1; + } + + // Only proceed if something is left + if (!(i > k || i === len)) { + newPts[0] = pts[i]; + RDP(pts, i, k, eps, newPts); + } + + return newPts; + }, + + /** + * 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(); + * + *

+ *
+         *
+         */
+        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-2014
+        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 
+    and .
+ */
+
+
+/*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) {
+                tmp = arr.slice(0);
+                tmp.sort(function (a, b) {
+                    return a - b;
+                });
+                len = tmp.length;
+
+                if (len % 2 === 1) {
+                    return tmp[parseInt(len * 0.5, 10)];
+                }
+
+                return (tmp[len * 0.5 - 1] + tmp[len * 0.5]) * 0.5;
+            }
+
+            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 standard deviation 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 weight. 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 NaN if not every element could be
+         * interpreted as a number and -Infinity 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 NaN if not every element could be
+         * interpreted as a number and Infinity 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)) {
+                len = arr.length;
+                res = [];
+
+                for (i = 0; i < len; i++) {
+                    res[i] = Math.abs(arr[i]);
+                }
+            } 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 % 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}.
+         * @param {Array} coords Array of {@link JXG.Coords}.
+         * @returns {Array} The stdform of the regression line.
+         * @memberof JXG.Math.Statistics
+         */
+        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];
+        }
+    };
+
+    return Mat.Statistics;
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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
+     * @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 norm 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;
+        },
+
+        /**
+         * Sort vertices counter clockwise starting with the point with the lowest y coordinate.
+         *
+         * @param {Array} p An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays.
+         *
+         * @returns {Array}
+         */
+        sortVertices: function (p) {
+            var i, ll,
+                ps = Expect.each(p, Expect.coordsArray),
+                N = ps.length;
+
+            // 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])) {
+                    ps = Type.swap(ps, i, 0);
+                }
+            }
+
+            // sort ps in increasing order of the angle the points and the ll make with the x-axis
+            ll = ps.shift();
+            ps.sort(function (a, b) {
+                // atan is monotonically increasing, as we are only interested in the sign of the difference
+                // evaluating atan is not necessary
+                var rad1 = Math.atan2(a[2] - ll[2], a[1] - ll[1]),
+                    rad2 = Math.atan2(b[2] - ll[2], b[1] - ll[1]);
+
+                return rad1 - rad2;
+            });
+
+            // put ll back into the array
+            ps.unshift(ll);
+
+            // put the last element also in the beginning
+            ps.unshift(ps[ps.length - 1]);
+
+            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-intersecting 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;
+                    } else {
+                        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.
+         * @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);
+            }
+        },
+
+        /**
+         * The vectors p2-p1 and i2-i1 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 p2-p1 and i2-i1 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 can see the point "p", true is returned. Otherwise 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: 
    + *
  • i==0: use the positive square root,
  • + *
  • i==1: use the negative square root.
+ * See further {@link JXG.Point#createIntersectionPoint}. + * @param {Boolean} alwaysintersect. Flag that determines if segements 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; + + if (el1.elementClass === Const.OBJECT_CLASS_CURVE && + el2.elementClass === Const.OBJECT_CLASS_CURVE) { + // curve - curve + /** @ignore */ + func = function () { + return that.meetCurveCurve(el1, el2, i, j, el1.board); + }; + + } else if ((el1.elementClass === Const.OBJECT_CLASS_CURVE && el2.elementClass === Const.OBJECT_CLASS_LINE) || + (el2.elementClass === Const.OBJECT_CLASS_CURVE && el1.elementClass === Const.OBJECT_CLASS_LINE)) { + // curve - line (this includes intersections between conic sections and lines + /** @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, first2, last1, last2; + + first1 = first2 = Type.evaluate(el1.visProp.straightfirst); + last1 = last2 = Type.evaluate(el1.visProp.straightlast); + + /** + * If one of the lines is a segment or ray and + * the the intersection point shpould 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, + el1.board + ); + + 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 + /** @ignore */ + func = function () { + return that.meet(el1.stdform, el2.stdform, i, el1.board); + }; + } + + return func; + }, + + /** + * 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. + * @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; + } + + if (Type.evaluate(cu.visProp.curvetype) === 'plot') { + v = this.meetCurveLineDiscrete(cu, li, nr, board, !alwaysIntersect); + } else { + v = this.meetCurveLineContinuous(cu, li, nr, board); + } + + 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.Geometry.meetCurveLineDiscrete}. + * + * @param {JXG.Curve} cu Curve + * @param {JXG.Line} li Line + * @param {Number} nr Will return the nr-th intersection point. + * @param {JXG.Board} board + * + */ + meetCurveLineContinuous: function (cu, li, nr, board, testSegment) { + var t, func0, func1, func0a, 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 = 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, [tnew, tnew + delta]); + 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, [t - delta, t + delta]); + + 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, continuous case. + * Segments are treated as lines. Finding the nr-the intersection point + * works for nr=0,1 only. + * + * @private + * @deprecated + * @param {JXG.Curve} cu Curve + * @param {JXG.Line} li Line + * @param {Number} nr Will return the nr-th intersection point. + * @param {JXG.Board} board + * + * BUG: does not respect cu.minX() and cu.maxX() + */ + meetCurveLineContinuousOld: function (cu, li, nr, board) { + var t, t2, i, func, z, + tnew, steps, delta, tstart, tend, cux, cuy, + eps = Mat.eps * 10; + + JXG.deprecated('Geometry.meetCurveLineContinuousOld()', 'Geometry.meetCurveLineContinuous()'); + func = function (t) { + var v = li.stdform[0] + li.stdform[1] * cu.X(t) + li.stdform[2] * cu.Y(t); + return v * v; + }; + + // Find some intersection point + if (this.meetCurveLineContinuous.t1memo) { + tstart = this.meetCurveLineContinuous.t1memo; + t = Numerics.root(func, tstart); + } else { + tstart = cu.minX(); + tend = cu.maxX(); + t = Numerics.root(func, [tstart, tend]); + } + + this.meetCurveLineContinuous.t1memo = t; + cux = cu.X(t); + cuy = cu.Y(t); + + // Find second intersection point + if (nr === 1) { + if (this.meetCurveLineContinuous.t2memo) { + tstart = this.meetCurveLineContinuous.t2memo; + } + t2 = Numerics.root(func, tstart); + + if (!(Math.abs(t2 - t) > 0.1 && Math.abs(cux - cu.X(t2)) > 0.1 && Math.abs(cuy - cu.Y(t2)) > 0.1)) { + steps = 20; + delta = (cu.maxX() - cu.minX()) / steps; + tnew = cu.minX(); + + for (i = 0; i < steps; i++) { + t2 = Numerics.root(func, [tnew, tnew + delta]); + + if (Math.abs(func(t2)) <= eps && Math.abs(t2 - t) > 0.1 && Math.abs(cux - cu.X(t2)) > 0.1 && Math.abs(cuy - cu.Y(t2)) > 0.1) { + break; + } + + tnew += delta; + } + } + t = t2; + this.meetCurveLineContinuous.t2memo = t; + } + + // Is the point on the line? + if (Math.abs(func(t)) > eps) { + z = 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++) { + 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); + + i += 2; + } 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 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. + * + * @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]; + }, + + /** + * Intersection of two segments. + * @param {Array} p1 First point of segment 1 using homogeneous coordinates [z,x,y] + * @param {Array} p2 Second point of segment 1 using homogeneous coordinates [z,x,y] + * @param {Array} q1 First point of segment 2 using homogeneous coordinates [z,x,y] + * @param {Array} q2 Second point of segment 2 using homogeneous coordinates [z,x,y] + * @returns {Array} [Intersection point, t, u] The first entry contains the homogeneous coordinates + * of the intersection point. The second and third entry gives the position of the intersection between the + * two defining points. For example, the second entry t is defined by: intersection point = t*p1 + (1-t)*p2. + **/ + meetSegmentSegment: function (p1, p2, q1, q2) { + var t, u, diff, + li1 = Mat.crossProduct(p1, p2), + li2 = Mat.crossProduct(q1, q2), + c = Mat.crossProduct(li1, li2), + denom = c[0]; + + if (Math.abs(denom) < Mat.eps) { + return [c, Infinity, Infinity]; + } + + diff = [q1[1] - p1[1], q1[2] - p1[2]]; + + // Because of speed issues, evalute the determinants directly + t = (diff[0] * (q2[2] - q1[2]) - diff[1] * (q2[1] - q1[1])) / denom; + u = (diff[0] * (p2[2] - p1[2]) - diff[1] * (p2[1] - p1[1])) / denom; + + 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, + redArr, blueArr, + bbr, bbb, + lenBlue = blue.numberPoints, //points.length, + lenRed = red.numberPoints, // points.length, + L = []; + + if (lenBlue < 4 || lenRed < 4) { + return [0, NaN, NaN]; + } + + for (i = 0; i < lenRed - 3; i += 3) { + p = red.points; + redArr = [ + [p[i].usrCoords[1], p[i].usrCoords[2]], + [p[i + 1].usrCoords[1], p[i + 1].usrCoords[2]], + [p[i + 2].usrCoords[1], p[i + 2].usrCoords[2]], + [p[i + 3].usrCoords[1], p[i + 3].usrCoords[2]] + ]; + + bbr = this._bezierBbox(redArr); + + for (j = 0; j < lenBlue - 3; j += 3) { + p = blue.points; + blueArr = [ + [p[j].usrCoords[1], p[j].usrCoords[2]], + [p[j + 1].usrCoords[1], p[j + 1].usrCoords[2]], + [p[j + 2].usrCoords[1], p[j + 2].usrCoords[2]], + [p[j + 3].usrCoords[1], p[j + 3].usrCoords[2]] + ]; + + bbb = this._bezierBbox(blueArr); + if (this._bezierOverlap(bbr, bbb)) { + L = L.concat(this.meetBeziersegmentBeziersegment(redArr, blueArr)); + 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 arrays A, B,C, each 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} point Point to project. + * @param {JXG.Line} line Line on that the point is projected. + * @param {JXG.Board} [board=point.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) { + // Homogeneous version + var v = [0, line.stdform[1], line.stdform[2]]; + + if (!Type.exists(board)) { + board = point.board; + } + + v = Mat.crossProduct(v, point.coords.usrCoords); + //return this.meetLineLine(v, line.stdform, 0, board); + 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, + 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 {JXG.Coords} The coordinates of the projection of the given point on the given graph. + */ + 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[0]; + }, + + /** + * 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 graph 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, tnew, fnew, fold, delta, steps, + 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' + minfunc = function (t) { + var dx = x - curve.X(t), + dy = y - curve.Y(t); + return dx * dx + dy * dy; + }; + + fold = minfunc(t); + steps = 50; + delta = (curve.maxX() - curve.minX()) / steps; + tnew = curve.minX(); + + for (i = 0; i < steps; i++) { + fnew = minfunc(tnew); + + if (fnew < fold || isNaN(fold)) { + t = tnew; + fold = fnew; + } + + tnew += delta; + } + + //t = Numerics.root(Numerics.D(minfunc), t); + t = Numerics.fminbr(minfunc, [t - delta, t + delta]); + + if (t < curve.minX()) { + t = curve.maxX() + t - curve.minX(); + } + + // Cyclically + if (t > curve.maxX()) { + t = curve.minX() + t - curve.maxX(); + } + + 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, + bestprojection; + + for (i = 0; i < len; i++) { + projection = JXG.Math.Geometry.projectCoordsToSegment( + p, + pol.vertices[i].coords.usrCoords, + pol.vertices[(i + 1) % len].coords.usrCoords + ); + + d = JXG.Math.Geometry.distance(projection[0], p, 3); + if (0 <= projection[1] && projection[1] <= 1 && d < d_best) { + bestprojection = projection[0].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 {JXG.Coords} The coordinates of the projection of the given point on the given turtle. + */ + projectPointToTurtle: function (point, turtle, board) { + var newCoords, t, x, y, i, dist, el, minEl, + 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) { + newCoords = this.projectPointToCurve(point, el); + dist = this.distance(newCoords.usrCoords, point.coords.usrCoords); + + if (dist < mindist) { + x = newCoords.usrCoords[1]; + y = newCoords.usrCoords[2]; + t = point.position; + 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); + }, + + /** + * 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'}); + * + *
+ *
+         */
+        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-2013
+        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
+    , ,
+    and .
+ */
+
+
+/*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, skipdir,
+            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;
+        }
+
+        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++;
+                        }
+
+                        skipdir();
+                    }
+                }
+            } catch (e) {
+                throw e;
+            }
+        }
+
+        skipdir = function () {
+            var crc, compSize, size, os, i, c,
+                tmp = [];
+
+            if ((gpflags & 8)) {
+                tmp[0] = readByte();
+                tmp[1] = readByte();
+                tmp[2] = readByte();
+                tmp[3] = readByte();
+
+                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) {
+                nextFile();
+            }
+
+            tmp[0] = readByte();
+            if (tmp[0] !== 8) {
+                return;
+            }
+
+            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) {
+                nextFile();
+            }
+        };
+
+        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 
+
+                 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-2013
+        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 
+    and .
+ */
+
+
+/*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-2013
+        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 
+    and .
+ */
+
+
+/*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..
+                        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're 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-2013
+        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 
+    and .
+ */
+
+
+/*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-2014
+        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 
+    and .
+ */
+
+
+/*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 ring.
+     * @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 1 or -1.
+         * @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-2013
+        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 
+    and .
+ */
+
+
+/*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:
+     * 
   var z1 = new JXG.Complex(1, 0);
+     *    var z2 = new JXG.Complex(0, 1);
+     *    z = JXG.C.add(z1, z1);
+ * z1 and z2 here remain unmodified. With the object oriented approach above this + * section the code would look like: + *
   var z1 = new JXG.Complex(1, 0);
+     *    var z2 = new JXG.Complex(0, 1);
+     *    var z = new JXG.Complex(z1);
+     *    z.add(z2);
+ * @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-2013 + 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 + and . + */ + + +/*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 (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 , + // Sven Neumann , + // Robert Dougherty and + // Alex Wade + // 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 protanopia, deuteranopia, and tritanopia. + * @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; + }; + + return JXG; +}); + +/* + Copyright 2008-2017 + 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 + and . + */ + + +/*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: + * [x1, y1, x2, y2] + * + * The canvas will be spanned from the upper left corner (1, y1) + * to the lower right corner (x2, y2). + * + * @name JXG.Board#boundingbox + * @type Array + * @default [-5, 5, 5, -5] + */ + boundingBox: [-5, 5, 5, -5], + + /** + * 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, + + /** + * 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#iniBoard}. + * + * @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] + }, + drawZero: false, + visible: 'inherit' + } + } + }, + + /** + * Display of navigation arrows and zoom buttons + * + * @name JXG.Board#showNavigation + * @type Boolean + * @default true + */ + showNavigation: true, + + /** + * Display of zoom buttons. 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 to force reload of a construction. + * Works only with the JessieCode tag + * + * @name JXG.Board#showReload + * @type Boolean + * @default false + */ + showReload: false, + + showScreenshot: false, + + 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 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 wifth 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'. + * If the rendering engine is not available JSXGraph tries to detect a different engine. + * + *

+ * 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 'svg' + */ + renderer: 'svg', + + /** + * 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, + + /** + * Allow user interaction by registering mouse and touch events. + * + * @name JXG.Board#registerEvents + * @type Boolean + * @default true + */ + registerEvents: true, + + /** + * Change redraw strategy in SVG rendering engine. + * + * 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 'svg' + */ + minimizeReflow: 'svg', + + /** + * 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: + *

+             * 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: false,     // allow zooming by mouse wheel or
+             *   				   // by pinch-to-toom gesture on touch devices
+             *   needShift: false, // 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.
+             * }
+             * 
+ * + * 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: false, + needShift: false, + 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: + *
+             * pan: {
+             *   enabled: true   // Allow panning
+             *   needTwoFingers: true, // panning is done with two fingers on touch devices
+             *   needShift: true, // mouse panning needs pressing of the shift key
+             * }
+             * 
+ * + * @name JXG.Board#pan + * @type Object + * @default + */ + pan: { + needShift: true, + needTwoFingers: false, + enabled: true + }, + + /** + * 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. + *

+ * Possible sub-attributes with default values are: + *

+             * 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
+             * }
+             * 
+ * + * @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 + } + /**#@-*/ + }, + + /** + * Options that are used by the navigation bar. + * + * Default values are + *
+         * JXG.Option.navbar: {
+         *   strokeColor: '#333333',
+         *   fillColor: 'transparent',
+         *   highlightFillColor: '#aaaaaa',
+         *   padding: '2px',
+         *   position: 'absolute',
+         *   fontSize: '14px',
+         *   cursor: 'pointer',
+         *   zIndex: '100',
+         *   right: '5px',
+         *   bottom: '5px'
+         * },
+         * 
+ */ + 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: '#0000ff', + + /** + * 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: '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, + + /** + * 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: + *
  • 0 for a solid line
  • + *
  • 1 for a dotted line
  • + *
  • 2 for a line with small dashes
  • + + + *
  • 3 for a line with medium dashes
  • + *
  • 4 for a line with big dashes
  • + *
  • 5 for a line with alternating medium and big dashes and large gaps
  • + *
  • 6 for a line with alternating medium and big dashes and small gaps
+ * @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. + * @see JXG.GeometryElement#clearTrace + * @see JXG.GeometryElement#traces + * @see JXG.GeometryElement#numTraces + * @type Boolean + * @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. + * @type Boolean + * @default true + * @name JXG.GeometryElement#scalable + */ + 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 ssimple elements + * consisting of one SVG node. + * @type Boolean + * @default false + * @name JXG.GeometryElement#scalable + */ + dragToTopOfLayer: false, + + /*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 + // 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, + label: { + }, + + /** + * Use the unicode character 0x2212, i.e. the HTML entity &minus; as minus sign. + * That is −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 and is parallel to either the x (i.e. y = 0) or the y (i.e. x = 0) 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 true, 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 1 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#equidistant + * @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 + * }); + *
+ *
+             */
+            insertTicks: false,
+            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 JXG.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 JXG.Ticks#maxLabelLength} this determines the precision used to shorten the tick label.
+             *
+             * @type Number
+             * @name Ticks#precision
+             * @see Ticks#maxLabelLength
+             * @default 3
+             */
+            precision: 3,
+
+            /**
+             * The default distance between two ticks. Please be aware that this value does not have
+             * to be used if {@link JXG.Ticks#insertTicks} is set to true.
+             *
+             * @type Number
+             * @name Ticks#ticksDistance
+             * @see Ticks#equidistant
+             * @see Ticks#insertTicks
+             * @default 1
+             */
+            ticksDistance: 1,
+            strokeOpacity: 1,
+            strokeWidth: 1,
+            strokeColor: 'black',
+            highlightStrokeColor: '#888888',
+            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
+            // close the meta tag
+            /**#@-*/
+        },
+
+         /*
+          *  Generic options used by {@link JXG.Hatch}
+          */
+        hatch: {
+            drawLabels: false,
+            drawZero: true,
+            majorHeight: 20,
+            anchor: 'middle',
+            strokeWidth: 2,
+            strokeColor: 'blue',
+            ticksDistance: 0.2
+        },
+
+        /**
+         * Precision options.
+         *
+         * The default values are
+         * 
+         * JXG.Options.precision: {
+         *   touch: 30,
+         *   touchMax: 100,
+         *   mouse: 4,
+         *   epsilon: 0.0001,
+         *   hasPoint: 4
+         * }
+         * 
+ */ + precision: { + touch: 30, + touchMax: 100, + mouse: 4, + epsilon: 0.0001, + hasPoint: 4 + }, + + /** + * Default ordering of the layers. + * + * The default values are + *
+         * 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
+         * }
+         * 
+ */ + 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 + }, + + /* special angle options */ + angle: { + /**#@+ + * @visprop + */ + + withLabel: true, + + /** + * Radius of the sector, displaying the angle. + * + * @type Number + * @name Angle#radius + * @default 0.5 + * @visprop + */ + radius: 0.5, + + /** + * 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: '#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: 'black', + size: 2, + face: 'o', + withLabel: false, + name: '' + }, + label: { + position: 'top', + offset: [0, 0], + strokeColor: '#0000FF' + }, + + arc: { + visible: false + } + + /**#@-*/ + }, + + /* special arc options */ + arc: { + /**#@+ + * @visprop + */ + + label: {}, + firstArrow: false, + lastArrow: false, + fillColor: 'none', + highlightFillColor: 'none', + strokeColor: '#0000ff', + highlightStrokeColor: '#C3D9FF', + useDirection: false + + /**#@-*/ + }, + + /* 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, + size: 8 + }, + strokeColor: '#666666', + highlightStrokeWidth: 1, + highlightStrokeColor: '#888888', + 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 + }, + + /** + * 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: 'black' + }, + + /** + * Attributes for second line. + * + * @type Line + * @name Bisectorlines#line2 + */ + line2: { // + strokeColor: 'black' + } + + /**#@-*/ + }, + + /* special button options */ + button: { + /**#@+ + * @visprop + */ + + /** + * Control the attribute "disabled" of the HTML button. + * + * @name disabled + * @memberOf Button.prototype + * + * @type Boolean + * @default false + */ + disabled: 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, + + /**#@-*/ + }, + + /*special circle options */ + circle: { + /**#@+ + * @visprop + */ + + /** + * If true, 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: '#0000ff', + highlightStrokeColor: '#C3D9FF', + + /** + * Attributes for center point. + * + * @type Point + * @name Circle#center + */ + center: { + 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: '#0000ff', + highlightStrokeColor: '#C3D9FF', + + /** + * Attributes for center point. + * + * @type Point + * @name Circumcircle#center + */ + center: { // center point + visible: false, + fixed: false, + withLabel: false, + name: '' + } + /**#@-*/ + }, + + circumcirclearc: { + /**#@+ + * @visprop + */ + + fillColor: 'none', + highlightFillColor: 'none', + strokeColor: '#0000ff', + 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: '#00FF00', + highlightFillColor: '#00FF00', + fillOpacity: 0.3, + highlightFillOpacity: 0.3, + strokeColor: '#0000ff', + 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: '#0000ff', + 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: '' + } + + /**#@-*/ + }, + + /* special curve options */ + curve: { + strokeWidth: 1, + strokeColor: '#0000ff', + 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
    + *
  • 'none'
  • + *
  • 'plot': Data plot
  • + *
  • 'parameter': we can not distinguish function graphs and parameter curves
  • + *
  • 'functiongraph': function graph
  • + *
  • 'polar'
  • + *
  • 'implicit' (not yet)
+ * 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 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 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, + + /** + * 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 + */ + doAdvancedPlotOld: false, + + /** + * Attributes for circle label. + * + * @type Label + * @name Circle#label + */ + label: { + position: 'lft' + } + + /**#@-*/ + }, + + 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: 'black', + 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 integer multiples to snapSizeX in x and snapSizeY in y direction. + * If this value is equal to or less than 0, 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 JXG.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 0, 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 JXG.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: '#0000ff', + highlightStrokeColor: '#C3D9FF', + + /** + * Attributes of circle center. + * + * @type Point + * @name Incircle#center + */ + center: { // center point + visible: false, + fixed: false, + withLabel: false, + name: '' + } + /**#@-*/ + }, + + inequality: { + /**#@+ + * @visprop + */ + + fillColor: 'red', + fillOpacity: 0.2, + strokeColor: 'none', + + /** + * By default an inequality is less (or equal) than. Set inverse to true 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 + strokeWidth: 0, + strokeOpacity: 0, + fillColor: 'red', + fillOpacity: 0.4, + highlightFillColor: '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: '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: '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 + + /**#@-*/ + }, + + /* 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: 'black', + strokeOpacity: 1, + highlightStrokeOpacity: 0.666666, + highlightStrokeColor: 'black', + + fixed: true, + + /** + * Possible string values for the position of a label for + * label anchor points are: + *
    + *
  • 'lft' + *
  • 'rt' + *
  • 'top' + *
  • 'bot' + *
  • 'ulft' + *
  • 'urt' + *
  • 'llft' + *
  • 'lrt' + *
+ * This is relevant for non-points: line, circle, curve. + * + * The names have been borrowed from MetaPost. + * + * @name Label#position + * @see Label#offset + * @type String + * @default 'urt' + */ + position: 'urt', + + /** + * Label offset from label anchor + * The label anchor is determined by JXG.GeometryElement#label.position + * + * @name Label#offset + * @see Label#position + * @type Array + * @default [10,10] + **/ + offset: [10, 10] + + /**#@-*/ + }, + + /* special legend options */ + legend: { + /** + * @visprop + */ + style: 'vertical', + labels: ['1', '2', '3', '4', '5', '6', '7', '8'], + colors: ['#B02B2C', '#3F4C6B', '#C79810', '#D15600', '#FFFF88', '#C3D9FF', '#4096EE', '#008C00'] + /**#@-*/ + }, + + /* special line options */ + line: { + /**#@+ + * @visprop + */ + + /** + * Line has an arrow head at the position of its first point or the corresponding + * intersection with the canvas border. + * + * @name Line#firstArrow + * @see Line#lastArrow + * @see Line#touchFirstPoint + * @type Boolean / Object + * @default false + */ + firstArrow: false, + + /** + * Line has an arrow head at the position of its second point or the corresponding + * intersection with the canvas border. + * + * @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). + * @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: '#0000ff', + highlightStrokeColor: '#888888', + 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 JXG.Point#snapSizeX} and {@link JXG.Point#snapSizeY}. + * + * @see Point#snapSizeX + * @see Point#snapSizeY + * @type Boolean + * @name Line#snapToGrid + * @default false + */ + snapToGrid: false, + + /** + * Defines together with {@link JXG.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 0, 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 JXG.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 0, 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 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: + *
    + *
  • 'butt', + *
  • 'round', + *
  • 'square'. + *
+ * 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 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 + * + * + * + * + * + * + * + * + * + * + *
Value
cross
circle
square
plus
diamond
triangleUp
triangleDown
triangleLeft
triangleRight
+ * + * @name Point#face + * + * @type string + * @see Point#setStyle + * @default circle + */ + face: 'o', + + /** + * Size of a point. + * Means radius resp. half the width of a point (depending on the face). + * + * @name Point#size + * + * @see Point#face + * @see Point#setStyle + * @type number + * @default 3 + */ + size: 3, + + 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 over, else not. + * + * @name Point#showInfobox + * + * @type Boolean + * @default true + */ + showInfobox: true, + + /** + * Truncating rule for the digits in the infobox. + *
    + *
  • 'auto': done automatically by JXG#autoDigits + *
  • 'none': no truncation + *
  • number: truncate after "number digits" with JXG.toFixed(); + *
+ * + * @name Point#infoboxDigits + * + * @type String, Number + * @default 'auto' + */ + 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 defined by + * {@link Point#snapSizeX} and {@link Point#snapSizeY}. + * + * @name Point#snapToGrid + * + * @see JXG.Point#snapSizeX + * @see JXG.Point#snapSizeY + * @type Boolean + * @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 0, 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 Board#defaultAxes + * @type Number + * @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 0, 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 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 true, moving the mouse over inner points triggers hasPoint. + * + * @see JXG.GeometryElement#hasPoint + * @name Polygon#hasInnerPoints + * @type Boolean + * @default false + */ + hasInnerPoints: false, + + fillColor: '#00FF00', + highlightFillColor: '#00FF00', + fillOpacity: 0.3, + highlightFillOpacity: 0.3, + + /** + * 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' + } + }, + + /** + * Attributes for the polygon vertices. + * + * @type Point + * @name Polygon#vertices + */ + vertices: { + layer: 9, + withLabel: false, + name: '', + strokeColor: '#ff0000', + fillColor: '#ff0000', + fixed: false + }, + + /** + * Attributes for the polygon label. + * + * @type Label + * @name Polygon#label + */ + label: { + offset: [0, 0] + } + + /**#@-*/ + }, + + /* 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 regular polygon options */ + regularpolygon: { + /**#@+ + * @visprop + */ + + /** + * If true, moving the mouse over inner points triggers hasPoint. + * @see JXG.GeometryElement#hasPoint + * + * @name RegularPolygon#hasInnerPoints + * @type Boolean + * @default false + */ + hasInnerPoints: false, + fillColor: '#00FF00', + highlightFillColor: '#00FF00', + fillOpacity: 0.3, + highlightFillOpacity: 0.3, + + /** + * 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: '#ff0000', + fillColor: '#ff0000', + 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: '#ffff00' + + /**#@-*/ + }, + + /* special sector options */ + sector: { + /**#@+ + * @visprop + */ + + fillColor: '#00FF00', + highlightFillColor: '#00FF00', + fillOpacity: 0.3, + highlightFillOpacity: 0.3, + highlightOnSector: false, + highlightStrokeWidth: 0, + + /** + * Attributes for sub-element arc. It is only available, if the sector is defined by three points. + * + * @type Arc + * @name Sector#arc + */ + 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] + } + + /**#@-*/ + }, + + /* special segment options */ + segment: { + /**#@+ + * @visprop + */ + + label: { + position: 'top' + } + /**#@-*/ + }, + + semicircle: { + /**#@+ + * @visprop + */ + + /** + * Attributes for center point of the semicircle. + * + * @type Point + * @name Semicircle#midpoint + */ + midpoint: { + visible: false, + withLabel: false, + fixed: false, + name: '' + } + + /**#@-*/ + }, + + /* special slider options */ + slider: { + /**#@+ + * @visprop + */ + + /** + * The slider only returns integer multiples of this value, e.g. for discrete values set this property to 1. For + * continuous results set this to -1. + * + * @memberOf Slider.prototype + * @name snapWidth + * @type Number + */ + snapWidth: -1, // -1 = deactivated + + /** + * The precision of the slider value displayed in the optional text. + * @memberOf Slider.prototype + * @name precision + * @type Number + * @default 2 + */ + precision: 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 + * @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 + * @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 + * @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, + fixed: true, + 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, + precision: 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, + fixed: true, + name: '', + strokeColor: '#000000', + highlightStrokeColor: '#888888' + }, + + /** + * Attributes for the slider label. + * + * @type Label + * @name Slider#label + */ + label: { + strokeColor: '#000000' + } + + /**#@-*/ + }, + + /* special options for slope triangle */ + slopetriangle: { + /**#@+ + * @visprop + */ + + fillColor: 'red', + fillOpacity: 0.4, + highlightFillColor: 'red', + highlightFillOpacity: 0.3, + + borders: { + lastArrow: { + type: 2, + size: 10 + } + }, + + /** + * 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. + * @memberOf Tapemeasure.prototype + * @name precision + * @type Number + * @default 2 + */ + precision: 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 + */ + fontSize: 12, + + /** + * 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: 'black', + highlightStrokeColor: 'black', + highlightStrokeOpacity: 0.666666, + + /** + * Default CSS properties of the HTML text element. + *

+ * 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. + *

+ * If a property which is set here should be overruled by a CSS class + * then this property should be removed here. + *

+ * The reason, why this attribute should be kept to its default value at all, + * is that screen dumps of SVG boards with board.renderer.dumpToCanvas() + * will ignore the font-family if it is set in a CSS class. + * It has to be set explicitly as style attribute. + *

+ * In summary, the order of priorities from high to low is + *

    + *
  1. JXG.Options.text.cssStyle + *
  2. JXG.Options.text.cssDefaultStyle + *
  3. JXG.Options.text.cssClass + *
+ * @example + * If all texts should get its font-family from the default CSS class + * before initializing the board + *
+             *   JXG.Options.text.cssDefaultStyle = '';
+             * 
+ * should be called. + * + * @name cssDefaultStyle + * @memberOf Text.prototype + * @default 'font-family: Arial, Helvetica, Geneva, sans-serif;' + * @type String + * @see JXG.Text#highlightCssDefaultStyle + * @see JXG.Text#cssStyle + * @see JXG.Text#highlightCssStyle + */ + cssDefaultStyle: 'font-family: Arial, Helvetica, Geneva, sans-serif;', + + /** + * Default CSS properties of the HTML text element in case of highlighting. + *

+ * The CSS properties which are set here, are haded over to the style property + * of the HTML text element. That means, they have higher property than any + * CSS class. + * + * @name cssDefaultStyle + * @memberOf Text.prototype + * @default 'font-family: Arial, Helvetica, Geneva, sans-serif;' + * @type String + * @see JXG.Text#cssDefaultStyle + * @see JXG.Text#cssStyle + * @see JXG.Text#highlightCssStyle + */ + highlightCssDefaultStyle: 'font-family: Arial, Helvetica, Geneva, sans-serif;', + + /** + * CSS properties of the HTML text element. + *

+ * The CSS properties which are set here, are haded 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 JXG.Text#cssDefaultStyle + * @see JXG.Text#highlightCssDefaultStyle + * @see JXG.Text#highlightCssStyle + */ + cssStyle: '', + + /** + * CSS properties of the HTML text element in case of highlighting. + *

+ * The CSS properties which are set here, are haded over to the style property + * of the HTML text element. That means, they have higher property than any + * CSS class. + * + * @name cssDefaultStyle + * @memberOf Text.prototype + * @default '' + * @type String + * @see JXG.Text#cssDefaultStyle + * @see JXG.Text#highlightCssDefaultStyle + * @see JXG.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. + * + * @name useMathJax + * @memberOf Text.prototype + * @default false + * @type Boolean + */ + useMathJax: false, + + /** + * Determines the rendering method of the text. Possible values + * include 'html' and 'internal. + * + * @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 'left', 'middle', and + * 'right'. + * + * @name anchorX + * @memberOf Text.prototype + * @default 'left' + * @type String + */ + anchorX: 'left', + + /** + * The vertical alignment of the text. Possible values include 'top', 'middle', and + * 'bottom'. + * + * @name anchorY + * @memberOf Text.prototype + * @default 'middle' + * @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. + * 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 0, 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 JXG.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 0, 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 JXG.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: '#ff0000' + } + /**#@-*/ + }, + + /** + * Abbreviations of properties. 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. + */ + 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); + }, + 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, + 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: validateInteger, + snapSizeX: validatePositive, + snapSizeY: validatePositive, + snapWidth: Type.isNumber, + snapToGrid: false, + snatchDistance: validateNotNegative, + straightFirst: false, + straightLast: false, + stretch: false, + strokeColor: validateColor, + strokeOpacity: Type.isNumber, + strokeWidth: 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: + * + * + * + * + * + * + * + * + * + * + * + *
InputOutput
cross, xx
circle, oo
square, [][]
plus, ++
diamond, <><>
triangleup, a, ^A
triangledown, vv
triangleleft, <<
triangleright, >>
+ * @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-2017 + 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 + and . + */ + + +/*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*/ + +/* 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"; + + /** + *

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.

+ *

The methods of this renderer can be divided into different categories: + *

+ *
Draw basic elements
+ *
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.
+ *
Draw primitives
+ *
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.
+ *
Attribute manipulation
+ *
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.
+ *
Renderer control
+ *
Methods to clear the drawing board or to stop and to resume the rendering engine.
+ *

+ * @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 8 + */ + this.vOffsetText = 0; + + /** + * If this property is set to true 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 false + * @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 supporting browsers are IE 9 - 11. + * @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 enhanced is set to true. + * @param {JXG.GeometryElement} element The element to update + * @param {Object} [not={}] Select properties you don't want to be updated: {fill: true, dash: true} updates + * everything except for fill and dash. Possible values are stroke, fill, dash, shadow, gradient. + * @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); + } + } else { + this.setDraft(el); + } + } + }, + + + /* ******************************** * + * 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)), + s1 = (size === 0) ? 0 : size + 1; + + if (!isNaN(el.coords.scrCoords[2] + el.coords.scrCoords[1])) { + size *= ((!el.board || !el.board.options.point.zoom) ? + 1.0 : Math.sqrt(el.board.zoomX * el.board.zoomY)); + + 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.hide(el); + } + + 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); + }, + + /** + * 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 {Number} strokeWidth Stroke width of the line. This determines the size of the + * arrow head. + * + * @returns {Object} Returns the object returned by + * {@link JXG.AbstractRenderer#getPositionArrowHead}. This contains the information in + * horizontal and vertical pixels how much + * the line has to be shortened on each end. + * + * @private + * @see Line + * @see JXG.Line + * @see JXG.AbstractRenderer#updateLine + * @see JXG.AbstractRenderer#getPositionArrowHead + * + */ + updateLineEndings: function(el, strokewidth) { + var 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), + obj, margin = null; + + margin = Type.evaluate(el.visProp.margin); + Geometry.calcStraight(el, c1, c2, margin); + + obj = this.getPositionArrowHead(el, c1, c2, strokewidth); + this.updateLinePrim(el.rendNode, + obj.c1.scrCoords[1] + obj.d1x, obj.c1.scrCoords[2] + obj.d1y, + obj.c2.scrCoords[1] - obj.d2x, obj.c2.scrCoords[2] - obj.d2y, el.board); + + return obj; + }, + + /** + * Read the attribute "size" of the arrow heads. Multiplied with the stroke width of the line + * this gives the absolute size of the arrow heads. Then the arrow heads are redrawn by the renderer. + * + * @param {JXG.Line} el Reference to a line object, that has to be drawn. + * @param {Object} obj Reference to a object returned by + * {@link JXG.AbstractRenderer#getPositionArrowHead} + * @returns {JXG.AbstractRenderer} Reference to the renderer + * + * @private + * @see Line + * @see JXG.Line + * @see JXG.AbstractRenderer#updateLine + * @see JXG.AbstractRenderer#getPositionArrowHead + */ + updateArrowSize: function(el, obj) { + var size, ev_fa, ev_la, obj; + + ev_fa = Type.evaluate(el.visProp.firstarrow); + if (ev_fa) { + if (Type.exists(ev_fa.size)) { + size = Type.evaluate(ev_fa.size); + } else { + size = 3; + } + + this._setArrowWidth(el.rendNodeTriangleStart, obj.sFirst, el.rendNode, size); + } + ev_la = Type.evaluate(el.visProp.lastarrow); + if (ev_la) { + if (Type.exists(ev_la.size)) { + size = Type.evaluate(ev_la.size); + } else { + size = 3; + } + this._setArrowWidth(el.rendNodeTriangleEnd, obj.sLast, el.rendNode, size); + } + + return this; + }, + + /** + * 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) { + var obj; + + obj = this.updateLineEndings(el, Type.evaluate(el.visProp.strokewidth)); + this.makeArrows(el); + this._updateVisual(el); + this.updateArrowSize(el, obj); + this.setLineCap(el); + }, + + /** + * Shorten the line length 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.Geometry#calcStraight}). + * @param {JXG.Coords} c2 Coords of the second point of the line (after {@link JXG.Geometry#calcStraight}). + * @return {object} Object containing how much the line has to be shortened. + * Data structure: {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, strokewidth) { + var s, s1, s2, d, d1x, d1y, d2x, d2y, + minlen = Mat.eps, + typeFirst, typeLast, + sFirst = 0, + sLast = 0, + ev_fa = Type.evaluate(el.visProp.firstarrow), + ev_la = Type.evaluate(el.visProp.lastarrow), + size; + + d1x = d1y = d2x = d2y = 0.0; + /* + Handle arrow heads. + + The 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) { + 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); + s = s1 + s2; + + // Handle touchlastpoint /touchfirstpoint + if (ev_la && 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; + c2 = new Coords(Const.COORDS_BY_SCREEN, [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], el.board); + } + } + if (ev_fa && 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; + c1 = new Coords(Const.COORDS_BY_SCREEN, [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], el.board); + } + } + + // Correct the position of the arrow heads + d1x = d1y = d2x = d2y = 0.0; + d = c1.distance(Const.COORDS_BY_SCREEN, c2); + + if (Type.exists(ev_fa.type)) { + typeFirst = Type.evaluate(ev_fa.type); + } + if (Type.exists(ev_la.type)) { + typeLast = Type.evaluate(ev_la.type); + } + + if (ev_fa) { + if (Type.exists(ev_fa.size)) { + size = Type.evaluate(ev_fa.size); + } else { + size = 3; + } + sFirst = strokewidth * size; + if (typeFirst === 2) { + sFirst *= 0.5; + minlen += strokewidth * size; + } else if (typeFirst === 3) { + sFirst = strokewidth; + minlen += strokewidth; + } else { + minlen += strokewidth * size; + } + } + if (ev_la) { + if (Type.exists(ev_la.size)) { + size = Type.evaluate(ev_la.size); + } else { + size = 3; + } + sLast = strokewidth * size; + if (typeLast === 2) { + sLast *= 0.5; + minlen += strokewidth * size; + } else if (typeLast === 3) { + sLast = strokewidth; + minlen += strokewidth; + } else { + minlen += strokewidth * size; + } + } + + if (ev_fa && + el.board.renderer.type !== 'vml') { + if (d >= minlen) { + d1x = (c2.scrCoords[1] - c1.scrCoords[1]) * sFirst / d; + d1y = (c2.scrCoords[2] - c1.scrCoords[2]) * sFirst / d; + } else { + sFirst = 0; + } + } + + if (ev_la && + el.board.renderer.type !== 'vml') { + + if (d >= minlen) { + d2x = (c2.scrCoords[1] - c1.scrCoords[1]) * sLast / d; + d2y = (c2.scrCoords[2] - c1.scrCoords[2]) * sLast / d; + } else { + sLast = 0.0; + } + } + } + + return { + c1: c1, + c2: c2, + d1x: d1x, + d1y: d1y, + d2x: d2x, + d2y: d2y, + sFirst: sFirst, + sLast: sLast + }; + }, + + /** + * Set the line endings (linecap) of a straight line. 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() { /* stub */ }, + + /** + * 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 */ }, + + /* ************************** + * 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'); + if (el.numberPoints > 1) { + this.makeArrows(el); + } + this._updateVisual(el, {shadow: true}, true); + 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) { + var w = Type.evaluate(el.visProp.strokewidth), + size, ev_fa, ev_la; + + 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); + } + + if (el.numberPoints > 1) { + this.makeArrows(el); + + ev_fa = Type.evaluate(el.visProp.firstarrow); + if (ev_fa) { + if (Type.exists(ev_fa.size)) { + size = Type.evaluate(ev_fa.size); + } else { + size = 3; + } + + this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode, size); + } + ev_la = Type.evaluate(el.visProp.lastarrow); + if (ev_la) { + if (Type.exists(ev_la.size)) { + size = Type.evaluate(ev_la.size); + } else { + size = 3; + } + this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode, size); + } + } + this._updateVisual(el); + + }, + + /* ************************** + * 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) { + var i; + //, len, polIsReal; + + // 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; + + 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 = ''; + 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, + 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 = Type.evaluate(el.visProp.anchorx); + + if (ax === 'right') { + v = Math.floor(el.board.canvasWidth - c); + } else if (ax === 'middle') { + v = Math.floor(c - 0.5 * el.size[0]); + } else { // 'left' + v = Math.floor(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 = Type.evaluate(el.visProp.anchory); + + if (ay === 'bottom') { + v = Math.floor(el.board.canvasHeight - c); + } else if (ay === 'middle') { + v = Math.floor(c - 0.5 * el.size[1]); + } else { // top + v = Math.floor(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 { + 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 + MathJax.Hub.Queue(['Typeset', MathJax.Hub, el.rendNode]); + } 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. + AMprocessNode(el.rendNode, false); + } + } + 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 = cssString.trim().replace(/;$/, '').split(";"); + + len = list.length; + for (i = 0; i < len; ++i) { + if (list[i].trim() !== '') { + s = list[i].split(':'); + key = s[0].replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }).trim(); + val = s[1].trim(); + 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, + 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 + 'px'; + } + } + } 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 (0,0) 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 el. + */ + 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); + }, + + + /* ************************** + * 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 createPrim 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 z-index 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 createPrim 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 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 createPrim 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 createPrim 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 createPrim method. + * @param {Node} node The path node. + * @param {String} pathString A string formatted like e.g. 'M 1,2 L 3,1 L5,5'. 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 rect and circle. 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: 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 GEONExT 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. #00ff00 or + * green 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, obj; + + 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) { + obj = this.updateLineEndings(el, sw); + this.makeArrows(el); + this.updateArrowSize(el, obj); + } + } + } + + 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, + obj, 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) { + obj = this.updateLineEndings(el, sw); + this.makeArrows(el); + this.updateArrowSize(el, obj); + } + + } + + 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)); + button.style.paddingLeft = '7px'; + button.style.paddingRight = '7px'; + + 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'); + + 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; + + // For XHTML we need unicode instead of HTML entities + + if (board.attr.showscreenshot) { + createButton(board.attr.screenshot.symbol, function () { + setTimeout(function() { + board.renderer.screenshot(board); + }, 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) { + return this.container.ownerDocument.getElementById(this.container.id + '_' + id); + }, + + /** + * 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; + + 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 canvas. + * Only available on SVGRenderer. + * + * @see JXG.SVGRenderer#dumpToCanvas + */ + dumpToCanvas: function(canvasId) {}, + + screenshot: function(board) {} + + }); + + return JXG.AbstractRenderer; +}); + +/* + Copyright 2008-2013 + 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 + and . + */ + + +/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */ +/*jslint nomen: true, plusplus: true, newcap:true*/ + +/* 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. + * @class JXG.AbstractRenderer + */ + JXG.NoRenderer = function () { + /** + * If this property is set to true 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 false + * @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.AbstractRenderer.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 el. + */ + 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 createPrim 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 z-index 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 createPrim 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 createPrim 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 createPrim 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 createPrim method. + * @param {Node} node The path node. + * @param {String} pathString A string formatted like e.g. 'M 1,2 L 3,1 L5,5'. 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 rect and circle. 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: 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 GEONExT 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. #00ff00 or green 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 () {}; + } + + }); + + JXG.NoRenderer.prototype = new AbstractRenderer(); + + return JXG.NoRenderer; +}); + +/* + Copyright 2008-2013 + 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 + and . + */ + + +/*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 = { + + /** + * 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 format equals 'raw' this has to be a callback function. + * @param {String} format The expected file format. Possible values are
+ *
raw
Raw text file. In this case board has to be a callback function.
+ *
geonext
Geonext File http://www.geonext.de
+ *
intergeo
Intergeo file format http://www.i2geo.net
+ *
tracenpoche
Tracenpoche construction http://www.tracenpoche.net
+ *
graph
Graph file
+ *
digraph
DiGraph file
+ *
geogebra
Geogebra File http://www.geogebra.org
+ *
cdy or cinderella
Cinderella (http://www.cinderella.de
+ *
+ * @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, callback) { + var request = false; + + if (!Type.exists(async)) { + async = true; + } + + //this.request = false; + + try { + request = new XMLHttpRequest(); + if (format.toLowerCase() === 'raw') { + request.overrideMimeType('text/plain; charset=iso-8859-1'); + } else { + request.overrideMimeType('text/xml; charset=iso-8859-1'); + } + } 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) { + 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 base64 encoded, zip-compressed string + text = Base64.decode(jxgBinFileReader(req)); + } else { + text = req.responseText; + } + this.parseString(text, board, format, callback); + } + }; + } + + this.cb = Type.bind(this.cbp, this); + request.onreadystatechange = this.cb; + + try { + request.send(null); + } catch (ex2) { + throw new Error("JSXGraph: A problem occurred while trying to read '" + url + "'."); + } + }, + + /** + * 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
+ *
raw
Raw text file. In this case board has to be a callback function.
+ *
geonext
Geonext File http://www.geonext.de
+ *
intergeo
Intergeo file format http://www.i2geo.net
+ *
tracenpoche
Tracenpoche construction http://www.tracenpoche.net
+ *
graph
Graph file
+ *
digraph
DiGraph file
+ *
geogebra
Geogebra File http://www.geogebra.org
+ *
cdy or cinderella
Cinderella (http://www.cinderella.de
+ *
+ * @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 { + 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('\n'); + } + + return JXG.FileReader; +}); + +/* + Copyright 2008-2013 + 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 + and . + */ + + +/*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 leftop^rightop into Math.pow(leftop,rightop). + * @param {String} te Expression of the form leftop^rightop + * @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 If(a,b,c) into (a)?(b):(c)/i>. + * @param {String} te Expression of the form If(a,b,c) + * @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 $(' and '). + * @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 GEONExT 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 GEONExT 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(/</g, '<'); + term = term.replace(/>/g, '>'); + term = term.replace(/&/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 GEONExT 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(/</g, '<'); + term = term.replace(/>/g, '>'); + term = term.replace(/&/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-2017 + 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 + and . + */ + + +/*global JXG: true, define: true*/ +/*jslint nomen: true, plusplus: 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', 'parser/geonext', 'utils/event', 'utils/color', 'utils/type' +], function (JXG, Const, Coords, Mat, Statistics, Options, GeonextParser, 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 JXG.OBJECT_TYPE_ value). + * @param {Number} oclass The element's class (a JXG.OBJECT_CLASS_ 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: + *
    + *
  • Elements with arrow tail or head: rendNodeTriangleStart, rendNodeTriangleEnd + *
  • SVG (or VML) texts: rendNodeText + *
  • Button: rendNodeForm, rendNodeButton, rendNodeTag + *
  • Checkbox: rendNodeForm, rendNodeCheckbox, rendNodeLabel, rendNodeTag + *
  • Input: rendNodeForm, rendNodeInput, rendNodeLabel, rendNodeTag + *
+ * + * 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: + * // <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;'> + * // </ellipse> + * + * @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 (true) 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 partuclar 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<p,p> + <b,p> + c = 0 + * For convenience we also store + * r: radius + * k: discriminant = sqrt(<b,b>-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' + }; + + /** + * 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]); + *
+ *
+         *
+         **/
+        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 (x, y). 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;
+                    } else {
+                        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 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:
  • callback: A function that is called as soon as the animation is finished.
+ * @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 visible on the board. + * @return {JXG.GeometryElement} Reference to the element + * @deprecated + * @private + */ + hideElement: function () { + JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()'); + + this.visPropCalc.visible = false; + this.board.renderer.display(this, false); + + if (Type.exists(this.label) && this.hasLabel) { + this.label.hiddenByParent = true; + if (this.label.visPropCalc.visible) { + this.label.hideElement(); + } + } + return this; + }, + + /** + * Make the element visible. + * @return {JXG.GeometryElement} Reference to the element + * @deprecated + * @private + */ + showElement: function () { + JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()'); + + this.visPropCalc.visible = true; + this.board.renderer.display(this, true); + + 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; + }, + + /** + * Set the visibility of an element. The visibility is influenced by + * (listed in ascending priority): + *
    + *
  1. The value of the element's attribute 'visible' + *
  2. 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'. + *
  3. being inside of the canvas + *
+ *

+ * This method is called three times for most elements: + *

    + *
  1. between {@link JXG.GeometryElement#update} + * and {@link JXG.GeometryElement#updateRenderer}. In case the value is 'inherit', nothing is done. + *
  2. Recursively, called by itself for child elements. Here, 'inherit' is overruled by the parent's value. + *
  3. In {@link JXG.GeometryElement#updateRenderer}, if the element is outside of the canvas. + *
+ * + * @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 property to value. + * @param {String} property The property's name. + * @param value The new value + * @private + */ + _set: function (property, value) { + 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 { + this.visProp[property] = value; + } + }, + + /** + * Resolves property shortcuts like color and expands them, e.g. strokeColor and fillColor. + * Writes the expanded properties back to the given properties. + * @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 it's 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, '>'); + 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, '>'); + 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. + * @param {Object} attributes An object with attributes. + * @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]; + oldvalue = this.visProp[key]; + + // 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 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); + + 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); + } + } + continue; + } + + 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)); + 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 { + 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(); + } + } else { + if (!this.label) { + this.createLabel(); + } + 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 'maxlength': + // input. Is not available on initial call. + if (Type.exists(this.rendNodeTag)) { + this.rendNodeTag.maxlength = !!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 key. + * @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.update(); + + // if (!Type.evaluate(this.visProp.visible)) { + // this.label.hiddenByParent = true; + // this.label.visPropCalc.visible = 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 http://jsxgraph.uni-bayreuth.de/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}. + */ + 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', [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; + }, + + /** + * 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, 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, ticks, + //i, len, g, el, p, + boardBB, + needsSnapToGrid = false, + sX = Type.evaluate(this.visProp.snapsizex), + sY = Type.evaluate(this.visProp.snapsizey); + + if (!Type.exists(this.coords)) { + return this; + } + + needsSnapToGrid = Type.evaluate(this.visProp.snaptogrid) || force === true; + + if (needsSnapToGrid) { + x = this.coords.usrCoords[1]; + y = this.coords.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) { + boardBB = this.board.getBoundingBox(); + x = Math.round(x / sX) * sX; + y = Math.round(y / sY) * sY; + + // checking whether x and y are still within boundingBox, + // if not, adjust them to remain within the board + 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; + }, + + /** + * 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 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 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 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 + * attribute:strokecolor. + * @name JXG.GeometryElement#attribute:<attribute> + * @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-2017 + 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 + and . + */ + + +/*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 + *
  • 'translate' + *
  • 'scale' + *
  • 'reflect' + *
  • 'rotate' + *
  • 'shear' + *
  • 'generic' + *
+ * @param {Object} params The parameters depend on the transformation type + * + *

+ * Translation matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( a  1  0) * ( x )
+     * ( b  0  1)   ( y )
+     * 
+ * + *

+ * Scale matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( 0  a  0) * ( x )
+     * ( 0  0  b)   ( y )
+     * 
+ * + *

+ * A rotation matrix with angle a (in Radians) + *

+     * ( 1    0        0      )   ( z )
+     * ( 0    cos(a)   -sin(a)) * ( x )
+     * ( 0    sin(a)   cos(a) )   ( y )
+     * 
+ * + *

+ * Shear matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( 0  1  a) * ( x )
+     * ( 0  b  1)   ( y )
+     * 
+ * + *

Generic transformation: + *

+     * ( a  b  c )   ( z )
+     * ( d  e  f ) * ( x )
+     * ( g  h  i )   ( y )
+     * 
+ * + */ + JXG.Transformation = function (board, type, params) { + this.elementClass = Const.OBJECT_CLASS_OTHER; + 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: 'bind', + melt: 'melt' + }; + }; + + JXG.Transformation.prototype = {}; + + JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ { + /** + * @private + * @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. + * + *

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 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 + * returning the angle in Radians and - optionally - a coordinate pair defining the + * reotation 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'. + * + *

A transformation with a generic matrix looks like: + *

+         * ( a  b  c )   ( z )
+         * ( d  e  f ) * ( x )
+         * ( g  h  i )   ( y )
+         * 
+ * + */ + 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) { + 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 { + 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, the do the matrix-vector-multiplication. + * @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. + * 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. In every update of the + * GeometryElement, the transformation is executed. + * @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 transformations. This only works if + * the both transformation matrices consist of numbers, solely (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 {number,function} The parameters depend on the transformation type, supplied as attribute 'type'. + * Possible transformation types are + *
  • 'translate' + *
  • 'scale' + *
  • 'reflect' + *
  • 'rotate' + *
  • 'shear' + *
  • 'generic' + *
+ * The transformation matrix then looks like: + *

+ * Translation matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( a  1  0) * ( x )
+     * ( b  0  1)   ( y )
+     * 
+ * + *

+ * Scale matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( 0  a  0) * ( x )
+     * ( 0  0  b)   ( y )
+     * 
+ * + *

+ * A rotation matrix with angle a (in Radians) + *

+     * ( 1    0        0      )   ( z )
+     * ( 0    cos(a)   -sin(a)) * ( x )
+     * ( 0    sin(a)   cos(a) )   ( y )
+     * 
+ * + *

+ * Shear matrix: + *

+     * ( 1  0  0)   ( z )
+     * ( 0  1  a) * ( x )
+     * ( 0  b  1)   ( y )
+     * 
+ * + *

Generic transformation: + *

+     * ( a  b  c )   ( z )
+     * ( d  e  f ) * ( x )
+     * ( g  h  i )   ( y )
+     * 
+ * + * @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'}); + * + *
+ *
+     *
+     * @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'});
+     *
+     * 
+ *
+     *
+     * @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'});
+     *
+     * 
+ *
+     *
+     * @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'});
+     *
+     * 
+ *
+     *
+     * @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'});
+     *
+     * 
+ *
+     *
+     * @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]);
+     *
+     * 
+ *
+     *
+     * @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));
+     *
+     * 
+ *
+     *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'options', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/coords', 'base/constants', 'base/element',
+    'parser/geonext', 'utils/type', 'base/transformation'
+], function (JXG, Options, Mat, Geometry, Numerics, Statistics, Coords, Const, GeometryElement, GeonextParser, Type, Transform) {
+
+    "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;
+
+        /**
+         * 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
+         * @name Glider#slideObject
+         */
+        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;
+
+        /**
+         * Dummy function for unconstrained points or gliders.
+         * @private
+         */
+        this.updateConstraint = function () {
+            return this;
+        };
+
+        /**
+         * 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 */ {
+        /**
+         * Updates the coordinates of the element.
+         * @private
+         */
+        updateCoords: function (fromParent) {
+            if (!this.needsUpdate) {
+                return this;
+            }
+
+            if (!Type.exists(fromParent)) {
+                fromParent = false;
+            }
+
+            /*
+             * 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 (fromParent) {
+                    this.updateGliderFromParent();
+                } else {
+                    this.updateGlider();
+                }
+            }
+
+            if (!Type.evaluate(this.visProp.frozen)) {
+                this.updateConstraint();
+            }
+            this.updateTransform();
+
+            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, ev_sel,
+                slide = this.slideObject;
+
+            this.needsUpdateFromParent = false;
+            if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
+                if (Type.evaluate(this.visProp.isgeonext)) {
+                    delta = 1.0;
+                }
+                //this.coords.setCoordinates(Const.COORDS_BY_USER,
+                //    Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false);
+                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.
+                // side-effect: this.position is overwritten
+                this.updateConstraint();
+                //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToTurtle(this, slide, this.board).usrCoords, false);
+                newCoords = Geometry.projectPointToTurtle(this, slide, this.board);
+                newPos = this.position;     // 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();
+
+                    if (slide.transformations.length > 0) {
+                        slide.updateTransformMatrix();
+                        invMat = Mat.inverse(slide.transformMat);
+                        c = Mat.matVecMult(invMat, this.coords.usrCoords);
+
+                        cp = (new Coords(Const.COORDS_BY_USER, c, this.board)).usrCoords;
+                        c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board);
+
+                        newCoords = c[0];
+                        newPos = c[1];
+                    } else {
+                        // side-effect: this.position is overwritten
+                        //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCurve(this, slide, this.board).usrCoords, false);
+                        newCoords = Geometry.projectPointToCurve(this, slide, this.board);
+                        newPos = this.position; // 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,
+                baseangle, alpha, angle, beta,
+                delta = 2.0 * Math.PI,
+                newPos;
+
+            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.
+                // side-effect: this.position is overwritten:
+                this.updateConstraint();
+                c  = Geometry.projectPointToTurtle(this, slide, this.board).usrCoords;
+            } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
+                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 (ev_ig) {
+                        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.
+                    // side-effect: this.position is overwritten
+                    this.updateConstraint();
+                    c = Geometry.projectPointToCurve(this, slide, this.board).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.Point} 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);
+                    } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
+                        projCoords = Geometry.projectPointToTurtle(this, el, this.board);
+                    }
+
+                    if (ev_a === '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 && this.slideObject === el)) {
+                            this.makeGlider(el);
+                        }
+
+                        break;       // bind the point to the first attractor in its list.
+                    } else {
+                        if (el === this.slideObject && d >= ev_sd) {
+                            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 ([z], x, y) 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 tv = (x, y).
+         * @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 side 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';
+                    }
+
+                    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;
+
+                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 
    + *
  • a JavaScript function,
  • + *
  • a string containing GEONExT syntax. This string will be converted into a JavaScript + * function here,
  • + *
  • a Number
  • + *
  • a pointer to a slider object. This will be converted into a call of the Value()-method + * of this slider.
  • + *
+ * @see JXG.GeonextParser#geonext2JS + */ + addConstraint: function (terms) { + var fs, i, v, t, + 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; + } + }; + // 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()]); + }; + // 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()]); + }; + } + + /** + * 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(); + } + + 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 = Type.evaluate(this.visProp.offset); + + if (Type.evaluate(this.visProp.islabel)) { + 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 = Type.evaluate(this.visProp.offset); + + if (Type.evaluate(this.visProp.islabel)) { + 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.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. + * @returns {JXG.CoordsElement} Reference to this object. + */ + updateTransform: function () { + 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 point. + * @param {JXG.GeometryElement} el + * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} + * or an array of {@link JXG.Transformation}s. + * @returns {JXG.Point} Reference to this point object. + */ + 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. + * @name Glider#startAnimation + * @see Glider#stopAnimation + * @function + */ + startAnimation: function (direction, stepCount) { + var that = this; + + if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) { + this.intervalCode = window.setInterval(function () { + that._anim(direction, stepCount); + }, 250); + + if (!Type.exists(this.intervalCount)) { + this.intervalCount = 0; + } + } + return this; + }, + + /** + * Stop animation. + * @name Glider#stopAnimation + * @see Glider#startAnimation + * @function + */ + 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 path 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.Point} Reference to the point. + */ + 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 where. + * The animation is done after time milliseconds. + * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition. + * @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.Point} Reference to itself. + * @see #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 where. After arriving at + * where the point moves back to where it started. The animation is done after time + * 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.Point} Reference to itself. + * @see #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. + * @see #startAnimation + * @see #stopAnimation + * @private + */ + _anim: function (direction, stepCount) { + var distance, slope, dX, dY, alpha, startPoint, newX, radius, + factor = 1; + + this.intervalCount += 1; + if (this.intervalCount > stepCount) { + this.intervalCount = 0; + } + + if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) { + distance = this.slideObject.point1.coords.distance(Const.COORDS_BY_SCREEN, this.slideObject.point2.coords); + slope = this.slideObject.getSlope(); + if (slope !== Infinity) { + alpha = Math.atan(slope); + dX = Math.round((this.intervalCount / stepCount) * distance * Math.cos(alpha)); + dY = Math.round((this.intervalCount / stepCount) * distance * Math.sin(alpha)); + } else { + dX = 0; + dY = Math.round((this.intervalCount / stepCount) * distance); + } + + if (direction < 0) { + startPoint = this.slideObject.point2; + + if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0) { + factor = -1; + } else if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] === 0) { + if (this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0) { + factor = -1; + } + } + } else { + startPoint = this.slideObject.point1; + + if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0) { + factor = -1; + } else if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] === 0) { + if (this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0) { + factor = -1; + } + } + } + + this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [ + startPoint.coords.scrCoords[1] + factor * dX, + startPoint.coords.scrCoords[2] + factor * 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]); + this.coords = Geometry.projectPointToCurve(this, this.slideObject, this.board); + } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) { + if (direction < 0) { + alpha = this.intervalCount / stepCount * 2 * Math.PI; + } else { + alpha = (stepCount - this.intervalCount) / stepCount * 2 * Math.PI; + } + + 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.isObject(coords[1]) || // Transformation + (Type.isArray(coords[1]) && coords[1].length > 0 && Type.isObject(coords[1][0])) + )) { // Array of transformations + + // Transformation + 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-2017 + 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 + and . + */ + + +/*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/coords', 'base/element', 'parser/geonext', 'math/statistics', + 'utils/env', 'utils/type', 'math/math', 'base/coordselement' +], function (JXG, Const, Coords, GeometryElement, GeonextParser, Statistics, 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; + + this.size = [1.0, 1.0]; + this.id = this.board.setId(this, 'T'); + + // Set text before drawing + this._setUpdateText(content); + this.updateText(); + + this.board.renderer.drawText(this); + this.board.finalizeAdding(this); + + 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, + 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 = Type.evaluate(this.visProp.anchorx); + 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 = Type.evaluate(this.visProp.anchory); + 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; + } + + 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 + */ + _setUpdateText: function (text) { + var updateText, resolvedText, + ev_p = Type.evaluate(this.visProp.parse), + ev_um = Type.evaluate(this.visProp.usemathjax); + + this.orgText = text; + if (Type.isFunction(text)) { + this.updateText = function () { + resolvedText = text().toString(); + if (ev_p && !ev_um) { + this.plaintext = this.replaceSub(this.replaceSup(this.convertGeonext2CSS(resolvedText))); + } else { + this.plaintext = resolvedText; + } + }; + } else if (Type.isString(text) && !ev_p) { + this.updateText = function () { + this.plaintext = text; + }; + } else { + 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) { + this.content = "'" + text + "'"; + } else { + // Converts GEONExT syntax into JavaScript string + // Short math is allowed + this.content = this.generateTerm(text, 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._setUpdateText(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. + } + + return this; + }, + + /** + * Defines new content but converts < and > 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. + * Update array this.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. + * In JSXGraph this.size is necessary for applying rotations in IE and + * for aligning text. + */ + updateSize: function () { + var tmp, s, 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)) { + 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') { + try { + tmp = node.getBBox(); + this.size = [tmp.width, tmp.height]; + } catch (e) {} + } 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 <sub> + * @param {String} te String containing _{}. + * @returns {String} Given string with _{} replaced by <sub>. + */ + 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 .... What is passed would get out anyway. + /*jslint regexp: true*/ + + while (i >= 0) { + te = te.substr(0, i) + te.substr(i).replace(/_\{/, ''); + j = te.substr(i).indexOf('}'); + if (j >= 0) { + te = te.substr(0, j) + te.substr(j).replace(/\}/, ''); + } + i = te.indexOf('_{'); + } + + i = te.indexOf('_'); + while (i >= 0) { + te = te.substr(0, i) + te.substr(i).replace(/_(.?)/, '$1'); + i = te.indexOf('_'); + } + + return te; + }, + + /** + * Replace ^{} by <sup> + * @param {String} te String containing ^{}. + * @returns {String} Given string with ^{} replaced by <sup>. + */ + 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 .... What is passed would get out anyway. + /*jslint regexp: true*/ + + while (i >= 0) { + te = te.substr(0, i) + te.substr(i).replace(/\^\{/, ''); + j = te.substr(i).indexOf('}'); + if (j >= 0) { + te = te.substr(0, j) + te.substr(j).replace(/\}/, ''); + } + i = te.indexOf('^{'); + } + + i = te.indexOf('^'); + while (i >= 0) { + te = te.substr(0, i) + te.substr(i).replace(/\^(.?)/, '$1'); + 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') { + 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 () { + 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 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). + * @private + * @see JXG.GeonextParser.geonext2JS. + */ + generateTerm: function (contentStr, expand) { + 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(/&arc;/g, '∠'); + contentStr = contentStr.replace(//g, '∠'); + contentStr = contentStr.replace(/<arc\s*\/>/g, '∠'); + contentStr = contentStr.replace(/<sqrt\s*\/>/g, '√'); + + contentStr = contentStr.replace(/<value>/g, ''); + contentStr = contentStr.replace(/<\/value>/g, ''); + + // Convert GEONExT syntax into JavaScript syntax + i = contentStr.indexOf(''); + j = contentStr.indexOf(''); + 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); + } + 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(''); + j = contentStr.indexOf(''); + } + } + + plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"'; + plaintext = this.convertGeonext2CSS(plaintext); + + // This should replace &pi; by π + plaintext = plaintext.replace(/&/g, '&'); + plaintext = plaintext.replace(/"/g, "'"); + + return plaintext; + }, + + /** + * Converts the GEONExT tags and to + * HTML span tags with proper CSS formating. + * @private + * @see JXG.Text.generateTerm @see JXG.Text._setText + */ + convertGeonext2CSS: function (s) { + if (Type.isString(s)) { + s = s.replace(//g, ''); + s = s.replace(/<overline>/g, ''); + s = s.replace(/<\/overline>/g, ''); + s = s.replace(/<\/overline>/g, ''); + s = s.replace(//g, ''); + s = s.replace(/<arrow>/g, ''); + s = s.replace(/<\/arrow>/g, ''); + s = s.replace(/<\/arrow>/g, ''); + } + + 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(/<value>/g, ''); + content = content.replace(/<\/value>/g, ''); + + do { + search = /([\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]; + } else { + return [c[1], c[2] + this.size[1] / this.board.unitY, c[1] + this.size[0] / this.board.unitX, c[2]]; + } + } + }); + + /** + * @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. + *

+ * Parent elements can be two or three elements of type number, a string containing a GEONExT + * 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. + *

+ * 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"]); + *

+ *
+     * @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);}
+     *                        ]
+     *                     );
+     * 
+ *
+     * @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});
+     *
+     * 
+ *
+     *
+     */
+    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 (Type.evaluate(attr.rotate) !== 0 && attr.display === 'internal') {
+            t.addRotation(Type.evaluate(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],
+            '
' + + '' + + '
']; + + t = JXG.createText(board, par, attr); + t.type = Type.OBJECT_TYPE_HTMLSLIDER; + + t.rendNodeForm = t.rendNode.childNodes[0]; + t.rendNodeForm.id = t.rendNode.id + '_form'; + + t.rendNodeRange = t.rendNodeForm.childNodes[0]; + t.rendNodeRange.id = t.rendNode.id + '_range'; + 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.id = t.rendNode.id + '_out'; + t.rendNodeOut.value = parents[1][1]; + + 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 () { + var r, i, + uuid = [], + rnd = 0; + + 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 uuid.join(''); + }; + + return JXG.Util; +}); +/* + JessieCode Interpreter and Compiler + + Copyright 2011-2016 + 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 + and . + */ + +/*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/geometry', 'math/statistics', 'utils/type', 'utils/uuid', 'utils/env' +], function (JXG, Const, Text, Mat, 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 code 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 console 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(); + + /** + * 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 = {name: (that.lhs[that.scope] !== 0 ? that.lhs[that.scope] : '')}; + } + 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. + * @param {String} vname Name of the variable + * @param {Boolean} [local=false] Only look up the internal symbol table and don't look for + * the vname in Math or the element list. + */ + getvar: function (vname, local) { + var s; + + local = Type.def(local, false); + + s = this.isLocalVariable(vname); + if (s !== null) { + return s.locals[vname]; + } + + // check for an element with this name + if (this.isCreator(vname)) { + return this.creator(vname); + } + + if (this.isBuiltIn(vname)) { + return this.builtIn[vname]; + } + + if (this.isMathMethod(vname)) { + return Math[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 = ''; + + 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. + return this.builtIn[vname].src || this.builtIn[vname]; + } + + if (this.isMathMethod(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; + } + + return ''; + }, + + /** + * Adds the property isMap 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 node_op and value op_map or op_function into a executable + * function. + * @param {Object} node + * @returns {function} + */ + defineFunction: function (node) { + var fun, i, + 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]); + + 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*/ + + 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 { + 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 what of o to value + * @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); + } 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. + * @private + * @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 (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 (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>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 node for stationary 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; + + 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]; + }, + + /** + * Resolves the lefthand side of an assignment operation + * @param node + * @returns {Object} An object with two properties. o which contains the object, and + * a string what 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[1]; + + 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.'); + } + + fun = this.defineFunction(node); + fun.isMap = true; + + ret = fun; + break; + case 'op_function': + // parse the parameter list + // after this, the parameters are in pstack + + 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 + fun = this.execute(node.children[0]); + + // 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++) { + 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_equ': + // == 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_grt': + ret = this.execute(node.children[0]) > this.execute(node.children[1]); + break; + case 'op_lot': + ret = this.execute(node.children[0]) < this.execute(node.children[1]); + break; + case 'op_gre': + ret = this.execute(node.children[0]) >= this.execute(node.children[1]); + break; + case 'op_loe': + 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': + ret = this.getvar(node.value); + break; + + case 'node_const': + 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'; + 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('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 : ''); + + // 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_equ': + 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_grt': + ret = '(' + this.compile(node.children[0], js) + ' > ' + this.compile(node.children[1], js) + ')'; + break; + case 'op_lot': + ret = '(' + this.compile(node.children[0], js) + ' < ' + this.compile(node.children[1], js) + ')'; + break; + case 'op_gre': + ret = '(' + this.compile(node.children[0], js) + ' >= ' + this.compile(node.children[1], js) + ')'; + break; + case 'op_loe': + 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'; + } + + return ret; + }, + + /** + * 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 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); + }, + + /** + * + 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 (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 (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 (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 (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 (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 (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); + + return Math.pow(a, b); + }, + + 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, + X: that.X, + Y: that.Y, + V: that.V, + L: that.L, + dist: that.dist, + rad: Geometry.rad, + deg: Geometry.trueAngle, + factorial: Mat.factorial, + trunc: Type.trunc, + log: Mat.log, + ln: Math.log, + log10: Mat.log10, + lg: Mat.log10, + log2: Mat.log2, + lb: Mat.log2, + ld: Mat.log2, + cosh: Mat.cosh, + sinh: Mat.sinh, + IfThen: that.ifthen, + 'import': that.importModule, + 'use': that.use, + 'remove': that.del, + '$': that.getElementById, + '$board': that.board, + '$log': that.log, + D: that.DDD + }; + + // 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.dist.src = '$jc$.dist'; + builtIn.rad.src = 'JXG.Math.Geometry.rad'; + builtIn.deg.src = 'JXG.Math.Geometry.trueAngle'; + builtIn.factorial.src = 'JXG.Math.factorial'; + builtIn.trunc.src = 'JXG.trunc'; + builtIn.ln.src = 'Math.log'; + builtIn.log10.src = 'JXG.Math.log10'; + builtIn.lg.src = 'JXG.Math.log10'; + builtIn.log2.src = 'JXG.Math.log2'; + builtIn.lb.src = 'JXG.Math.log2'; + builtIn.ld.src = 'JXG.Math.log2'; + builtIn.cosh.src = 'JXG.Math.cosh'; + builtIn.sinh.src = 'JXG.Math.sinh'; + 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); })'; + if (builtIn.$board) { + builtIn.$board.src = '$jc$.board'; + } + builtIn.$log.src = '$jc$.log'; + + return builtIn; + }, + + /** + * 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 + '
'; + } + }, + + /** + * 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 + '
'; + } + }, + + _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.17 */ +/* + 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,149],$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,166],$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,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_equ', $$[$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_lot', $$[$0-2], $$[$0]); this.$.isMath = false; +break; +case 38: + this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_grt', $$[$0-2], $$[$0]); this.$.isMath = false; +break; +case 39: + this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_loe', $$[$0-2], $$[$0]); this.$.isMath = false; +break; +case 40: + this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_gre', $$[$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', {}); +break; +case 80: + this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_proplst_val', $$[$0-1]); +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 103: + this.$ = [$$[$0]]; +break; +case 95: case 99: case 104: + 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-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},{20:$VR,92:140},{8:$V2,20:$V8,31:109,44:141,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: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},o($Vs,[2,79]),{83:[1,145],86:[1,146]},o($VS,[2,81]),{35:[1,147]},{35:[2,84]},{35:[2,85]},{35:[2,86]},o($Vs,[2,77]),{66:[1,148],86:$VT},o($VU,[2,98]),{8:$V2,20:$V8,31:109,49:150,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: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,50:$Vc,51:$Vd,52:152,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},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,155]},{10:[1,156]},{16:[1,157]},{8:[1,158]},o($Vr,[2,10]),o($Vs,[2,25]),o($Vs,[2,49]),{35:[1,159]},o($Vx,[2,29],{39:$Vy}),o($Vs,[2,59]),{66:[1,160]},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,161]}),o($Vs,[2,94]),o($Vs,[2,96]),o($Vs,[2,97]),o($VV,[2,92]),{10:[1,162],86:$VT},{66:[1,163]},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:164,26:$Vb},{10:[1,165],86:$VW},o($VX,[2,103]),{10:[1,167],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:168,87:96},{8:$V2,20:$V8,29:169,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:170,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:171,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:172,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:173,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:174,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:175,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:176},o($VV,[2,93]),o($Vs,[2,90]),o($Vs,[2,100]),{25:177,26:$Vb},{20:[1,178]},{94:[1,179]},o($VS,[2,82]),o($VS,[2,83]),o($VU,[2,99]),o($Vq,[2,2],{12:[1,180]}),o($Vr,[2,4]),{16:[1,181]},{10:[1,182]},o($Vs,[2,27]),o($Vs,[2,95]),o($Vs,[2,101]),o($VX,[2,104]),{8:$V2,9:183,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:184,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:185,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,186]},o($Vs,[2,102]),o($Vr,[2,3]),{10:[1,187]},o($Vr,[2,6]),{6:6,7:$V1,8:$V2,9:20,11:188,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 { + function _parseError (msg, hash) { + this.message = msg; + this.hash = hash; + } + _parseError.prototype = Error; + + throw new _parseError(str, hash); + } +}, +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 82 +break; +case 23:return 83 +break; +case 24:return 26 +break; +case 25:return 27 +break; +case 26:return 16 +break; +case 27:return '#' +break; +case 28:return 34 +break; +case 29:return 35 +break; +case 30:return 79 +break; +case 31:return 64 +break; +case 32:return 65 +break; +case 33:return 66 +break; +case 34:return 8 +break; +case 35:return 10 +break; +case 36:return 58 +break; +case 37:return 57 +break; +case 38:return 53 +break; +case 39:return 54 +break; +case 40:return 55 +break; +case 41:return 50 +break; +case 42:return 51 +break; +case 43:return 47 +break; +case 44:return 45 +break; +case 45:return 48 +break; +case 46:return 46 +break; +case 47:return 41 +break; +case 48:return 43 +break; +case 49:return 42 +break; +case 50:return 39 +break; +case 51:return 37 +break; +case 52:return 32 +break; +case 53:return 86 +break; +case 54:return 5 +break; +case 55:return 20 +break; +case 56: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],"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-2017 + 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 + and . + */ + + +/*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', 'math/numerics', 'base/coords', 'base/constants', 'base/element', + 'parser/geonext', 'utils/type', 'base/transformation', 'base/coordselement' +], function (JXG, Options, Mat, Geometry, Numerics, Coords, Const, GeometryElement, GeonextParser, Type, Transform, 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.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; + r = parseFloat(Type.evaluate(this.visProp.size)) + + parseFloat(Type.evaluate(this.visProp.strokewidth)) * 0.5; + if (r < this.board.options.precision.hasPoint) { + r = this.board.options.precision.hasPoint; + } + + 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. + * @returns {JXG.CoordsElement} Reference to this object. + */ + updateTransform: function () { + var c, i; + + if (this.transformations.length === 0 || this.baseElement === null) { + return this; + } + + // case of bindTo + if (this === this.baseElement) { + c = this.transformations[0].apply(this.baseElement, 'self'); + // case of board.create('point',[baseElement,transform]); + } 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. + * TODO docu. + * @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}); + }, + + // 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); + + 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 GEONExT + * 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 GEONExT + * 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]); + *
+ *
+     * @example
+     * // Create a constrained point using anonymous function
+     * var p2 = board.create('point', [3.5, function () { return p1.X(); }]);
+     * 
+ *
+     * @example
+     * // Create a point using transformations
+     * var trans = board.create('transform', [2, 0.5], {type:'scale'});
+     * var p3 = board.create('point', [p2, trans]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     * @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]);
+     * 
+ *
+     */
+    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 This element is used to provide a constructor for an 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.
+     * @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: 
    + *
  • i==0: use the positive square root,
  • + *
  • i==1: use the negative square root.
+ * @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]); + *
+ *
+     */
+    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]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     * @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]);
+     * 
+ *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*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 {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. If null or an
+     * empty string is given, an unique name will be generated.
+     * @param {Boolean} withLabel construct label, yes/no
+     * @param {Number} layer display layer [0-9]
+     * @see JXG.Board#generateName
+     */
+    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
+         * udpate 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 arrow as child to defining points */
+        this.point1.addChild(this);
+        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',
+            addTicks: 'addTicks',
+            removeTicks: 'removeTicks',
+            removeAllTicks: 'removeAllTicks'
+        });
+    };
+
+    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,
+                sw = Type.evaluate(this.visProp.strokewidth);
+
+            prec = this.board.options.precision.hasPoint + 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.straightfirs) &&
+                    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 accomplishrd 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 deividing 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 = 0;
+                } 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;
+                }
+            }
+
+            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);
+
+            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])];
+        },
+
+        /**
+         * Adds ticks to this line. Ticks can be added to 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;
+        },
+
+        // documented in GeometryElement.js
+        remove: function () {
+            this.removeAllTicks();
+            GeometryElement.prototype.remove.call(this);
+        },
+
+        /**
+         * Removes all ticks from a line.
+         */
+        removeAllTicks: function () {
+            var t;
+
+            for (t = this.ticks.length; t > 0; t--) {
+                this.removeTicks(this.ticks[t - 1]);
+            }
+
+            this.ticks = [];
+            this.board.update();
+        },
+
+        /**
+         * Removes ticks identified by parameter named tick from this line.
+         * @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;
+            }
+
+            for (t = this.ticks.length; t > 0; t--) {
+                if (this.ticks[t - 1] === tick) {
+                    this.board.removeObject(this.ticks[t - 1]);
+
+                    if (this.ticks[t - 1].ticks) {
+                        for (j = 0; j < this.ticks[t - 1].ticks.length; j++) {
+                            if (Type.exists(this.ticks[t - 1].labels[j])) {
+                                this.board.removeObject(this.ticks[t - 1].labels[j]);
+                            }
+                        }
+                    }
+
+                    delete this.ticks[t - 1];
+                    break;
+                }
+            }
+        },
+
+        // 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} c,a,b A line can also be created providing three numbers. The line is then described by
+     * the set of solutions of the equation a*x+b*y+c*z = 0. 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.
+     * @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]]);
+     * 
+ *
+     * @example
+     * // Create a line using three coordinates
+     * var l1 = board.create('line', [1.0, -2.0, 3.0]);
+     * 
+ *
+     */
+    JXG.createLine = function (board, parents, attributes) {
+        var ps, el, p1, p2, i, attr,
+            c = [],
+            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 {
+                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 (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 {
+                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 JXG.Line#straightFirst}
+     * and {@link JXG.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 a 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]);
+     * 
+ *
+     *
+     * @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();} ]);
+     * 
+ *
+     *
+     */
+    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 JXG.Line#straightFirst}
+     * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.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 a*x+b*y+c*z = 0.
+     * @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]);
+     * 
+ *
+     */
+    JXG.createArrow = function (board, parents, attributes) {
+        var el;
+
+        attributes.firstArrow = false;
+        attributes.lastArrow = true;
+        el = board.create('line', parents, attributes).setStraight(false, false);
+        //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 JXG.Line#straightFirst}
+     * and {@link JXG.Line#straightLast} properties set to true. Additionally {@link JXG.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 a*x+b*y+c*z = 0.
+     * @example
+     * // Create an axis providing two coord pairs.
+     *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
+     * 
+ *
+     */
+    JXG.createAxis = function (board, parents, attributes) {
+        var attr, attr_ticks, el, els, dist;
+
+        // Arrays oder Punkte, mehr brauchen wir nicht.
+        if ((Type.isArray(parents[0]) || Type.isPoint(parents[0])) && (Type.isArray(parents[1]) || Type.isPoint(parents[1]))) {
+            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;
+                }
+            }
+
+            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]);
+     * 
+ *
+     */
+    JXG.createTangent = function (board, parents, attributes) {
+        var p, c, g, f, j, el, tangent;
+
+        // One arguments: 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') {
+                g = c.X;
+                f = c.Y;
+                tangent = board.create('line', [
+                    function () {
+                        return -p.X() * Numerics.D(f)(p.position) + p.Y() * Numerics.D(g)(p.position);
+                    },
+                    function () {
+                        return Numerics.D(f)(p.position);
+                    },
+                    function () {
+                        return -Numerics.D(g)(p.position);
+                    }
+                ], attributes);
+                p.addChild(tangent);
+
+                // this is required for the geogebra reader to display a slope
+                tangent.glider = p;
+            } else {  // curveType 'plot'
+                // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2
+                tangent = board.create('line', [
+                    function () {
+                        var i = Math.floor(p.position);
+
+                        if (i === c.numberPoints - 1) {
+                            i--;
+                        }
+
+                        if (i < 0) {
+                            return 1;
+                        }
+
+                        return c.Y(i) * c.X(i + 1) - c.X(i) * c.Y(i + 1);
+                    },
+                    function () {
+                        var i = Math.floor(p.position);
+
+                        if (i === c.numberPoints - 1) {
+                            i--;
+                        }
+
+                        if (i < 0) {
+                            return 0;
+                        }
+
+                        return c.Y(i + 1) - c.Y(i);
+                    },
+                    function () {
+                        var i = Math.floor(p.position);
+
+                        if (i === c.numberPoints - 1) {
+                            i--;
+                        }
+
+                        if (i < 0) {
+                            return 0.0;
+                        }
+
+                        return c.X(i) - c.X(i + 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]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     * @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]);
+     * 
+ *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'parser/geonext', 'utils/type'
+], function (JXG, GeometryElement, Coords, Const, 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
+     * 
  • 'twoPoints' which means the circle is defined by its center and a point on the circle.
  • + *
  • 'pointRadius' which means the circle is defined by its center and its radius in user units
  • + *
  • 'pointLine' 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
  • + *
  • 'pointCircle' which means the circle is defined by its center and its radius given by the radius of another circle
+ * 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 + *
  • a point on the circle if method is 'twoPoints'
  • + *
  • a line if the method is 'pointLine'
  • + *
  • a circle if the method is 'pointCircle'
+ * @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 + *
  • 'twoPoints' which means the circle is defined by its center and a point on the circle.
  • + *
  • 'pointRadius' which means the circle is defined by its center and its radius given in user units or as term.
  • + *
  • 'pointLine' 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.
  • + *
  • 'pointCircle' which means the circle is defined by its center and its radius given by the radius of another circle.
+ * @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; + + 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(); + + 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') { + 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 = this.board.options.precision.hasPoint, + mp = this.center.coords.usrCoords, + p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board), + r = this.Radius(), + dx, dy, dist; + + 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 () { + 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(); + } + + 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; + + switch (Type.evaluate(this.visProp.label.position)) { + case 'lft': + x = c[1] - r; + y = c[2]; + break; + case 'llft': + x = c[1] - Math.sqrt(0.5) * r; + y = c[2] - Math.sqrt(0.5) * r; + break; + case 'rt': + x = c[1] + r; + y = c[2]; + break; + case 'lrt': + x = c[1] + Math.sqrt(0.5) * r; + y = c[2] - Math.sqrt(0.5) * r; + break; + case 'urt': + x = c[1] + Math.sqrt(0.5) * r; + y = c[2] + Math.sqrt(0.5) * 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] - Math.sqrt(0.5) * r; + y = c[2] + Math.sqrt(0.5) * 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); + + 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} X(t)= radius*cos(t)+centerX. + */ + 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} X(t)= radius*sin(t)+centerY. + */ + 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]); + *
+ *
+     * @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(); }]);
+     * 
+ *
+     */
+    JXG.createCircle = function (board, parents, attributes) {
+        var el, p, i, attr,
+            isDraggable = true;
+
+        p = [];
+        for (i = 0; i < parents.length; i++) {
+            if (Type.isPointType(board, parents[i])) {
+                p = p.concat(Type.providePoints(board, [parents[i]], attributes, 'circle', ['center']));
+                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]");
+        }
+
+        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-2017
+ 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 
+ and .
+ */
+
+
+/*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 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 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',
+            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. startpoint or area. This is used to
+         * access the element later on. There are some reserved names: elements, add, remove, update, prepareUpdate,
+         * updateRenderer, highlight, noHighlight, 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-2017
+        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 
+    and .
+ */
+
+
+/*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/statistics', 'math/numerics',
+    'math/geometry', 'parser/geonext', 'utils/type', 'base/transformation', 'math/qdt'
+], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Numerics, Geometry, GeonextParser, Type, Transform, 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;
+
+        /**
+         * 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'
+        });
+    };
+
+    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, [0, 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, 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 other calss 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 other calss 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, j, tX, tY,
+                res = [],
+                points, qdt,
+                steps = Type.evaluate(this.visProp.numberpointslow),
+                d = (this.maxX() - this.minX()) / steps,
+                prec = this.board.options.precision.hasPoint,
+                dist = Infinity,
+                ux2, uy2,
+                ev_ct,
+                suspendUpdate = true;
+
+            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;
+
+            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];
+            }
+
+            ev_ct = Type.evaluate(this.visProp.curvetype);
+            if (ev_ct === 'parameter' ||
+                    ev_ct === 'polar') {
+
+                prec = prec * prec;
+
+                // Brute force search for a point on the curve close to the mouse pointer
+                for (i = 0, t = this.minX(); 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.checkReal();
+
+                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();
+         *
+         * 
+ *
+         *
+         * @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.
+         *
+         * 
+ *
+         *
+         *
+         */
+        updateDataArray: function () {
+            // this used to return this, but we shouldn't rely on the user to implement it.
+        },
+
+        /**
+         * Computes for equidistant points on the x-axis the values
+         * of the function.
+         * If the mousemove event triggers this update, we use only few
+         * points. Otherwise, e.g. on mouseup, many points are used.
+         * @see JXG.Curve#update
+         * @returns {JXG.Curve} Reference to the curve object.
+         */
+        updateCurve: function () {
+            var len, mi, ma, x, y, i,
+                //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.updateTransform(this.points[i]);
+                    suspendUpdate = true;
+                }
+            // continuous x data
+            } else {
+                if (Type.evaluate(this.visProp.doadvancedplot)) {
+                    this.updateParametricCurve(mi, ma, len);
+                } else if (Type.evaluate(this.visProp.doadvancedplotold)) {
+                    this.updateParametricCurveOld(mi, ma, len);
+                } 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();
+                    this.updateParametricCurveNaive(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)) {
+                this.points = Numerics.RamerDouglasPeucker(this.points, 0.2);
+                this.numberPoints = this.points.length;
+            }
+
+            return this;
+        },
+
+        updateTransformMatrix: function () {
+            var t, c, 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;
+        },
+
+        /**
+         * Check if at least one point on the curve is finite and real.
+         **/
+        checkReal: function () {
+            var b = false, i, p,
+                len = this.numberPoints;
+
+            for (i = 0; i < len; i++) {
+                p = this.points[i].usrCoords;
+                if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) {
+                    b = true;
+                    break;
+                }
+            }
+            this.isReal = b;
+        },
+
+        /**
+         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is false.
+         * @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 (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().
+                this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false);
+                suspendUpdate = true;
+            }
+            return this;
+        },
+
+        /**
+         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is true.
+         * Since 0.99 this algorithm is deprecated. It still can be used if {@link JXG.Curve#doadvancedplotold} is true.
+         *
+         * @deprecated
+         * @param {Number} mi Left bound of curve
+         * @param {Number} ma Right bound of curve
+         * @returns {JXG.Curve} Reference to the curve object.
+         */
+        updateParametricCurveOld: function (mi, ma) {
+            var i, t, t0, d,
+                x, y, x0, y0, top, depth,
+                MAX_DEPTH, MAX_XDIST, MAX_YDIST,
+                suspendUpdate = false,
+                po = new Coords(Const.COORDS_BY_USER, [0, 0], this.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 (this.board.updateQuality === this.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, [this.X(t, suspendUpdate), this.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, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false);
+            x = po.scrCoords[1];
+            y = po.scrCoords[2];
+
+            pointStack[0] = [x, y];
+
+            top = 1;
+            depth = 0;
+
+            this.points = [];
+            this.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], this.board, false);
+
+            do {
+                distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y);
+                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, [this.X(t, suspendUpdate), this.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);
+                }
+
+                if (j > 1) {
+                    d = distFromLine(this.points[j - 2].scrCoords, [x, y], this.points[j - 1].scrCoords);
+                    if (d < 0.015) {
+                        j -= 1;
+                    }
+                }
+
+                this.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
+                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);
+
+            this.numberPoints = this.points.length;
+
+            return this;
+        },
+
+        /**
+         * Crude and cheap test if the segment defined by the two points (x0, y0) and (x1, y1) is
+         * outside the viewport of the board. All parameters have to be given in screen coordinates.
+         *
+         * @private
+         * @param {Number} x0
+         * @param {Number} y0
+         * @param {Number} x1
+         * @param {Number} y1
+         * @returns {Boolean} true if the given segment is outside the visible area.
+         */
+        isSegmentOutside: function (x0, y0, x1, y1) {
+            return (y0 < 0 && y1 < 0) || (y0 > this.board.canvasHeight && y1 > this.board.canvasHeight) ||
+                (x0 < 0 && x1 < 0) || (x0 > this.board.canvasWidth && x1 > this.board.canvasWidth);
+        },
+
+        /**
+         * Compares the absolute value of dx with MAXX and the absolute value of dy
+         * with MAXY.
+         *
+         * @private
+         * @param {Number} dx
+         * @param {Number} dy
+         * @param {Number} MAXX
+         * @param {Number} MAXY
+         * @returns {Boolean} true, if |dx| < MAXX and |dy| < MAXY.
+         */
+        isDistOK: function (dx, dy, MAXX, MAXY) {
+            return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx + dy);
+        },
+
+         /**
+         * @private
+         */
+        isSegmentDefined: function (x0, y0, x1, y1) {
+            return !(isNaN(x0 + y0) && isNaN(x1 + y1));
+        },
+
+        /**
+         * 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: function (pnt) {
+            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 = this.board.canvasWidth,
+                ch = this.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))) {
+                this.points.push(pnt);
+                this._lastCrds = pnt.copy('scrCoords');
+            }
+        },
+
+        /**
+         * Find the intersection of the asymptote for e.g. a log function
+         * with the canvas.
+         * @private
+         * @param  {Array} asymptote Asymptote line in standard form
+         * @param  {Array} box       Bounding box of the canavs
+         * @param  {Number} direction horizontal direction of the asymptote. If < 0 the asymptote
+         *  goes to the left, otherwise to the right.
+         * @returns {Array}           Homogeneous coordinate array of the intersection point.
+         */
+        _intersectWithBorder: function(asymptote, box, direction) {
+            var border, intersection, x, y;
+
+            if (direction <= 0) { // Intersect with left border
+                border = [-box[0], 1, 0];
+                intersection = Mat.crossProduct(border, asymptote);
+                if (intersection[0] !== 0.0) {
+                    x = intersection[1] / intersection[0];
+                    y = intersection[2] / intersection[0];
+                } else {
+                    y = Infinity;
+                }
+
+                if (y < box[3]) { // Intersect with bottom border
+                    border = [-box[3], 0, 1];
+                    intersection = Mat.crossProduct(border, asymptote);
+                    if (intersection[0] !== 0.0) {
+                        x = intersection[1] / intersection[0];
+                        y = intersection[2] / intersection[0];
+                    } else {
+                        x = Infinity;
+                    }
+                } else if (y > box[1]) { // Intersect with top border
+                    border = [-box[1], 0, 1];
+                    intersection = Mat.crossProduct(border, asymptote);
+                    if (intersection[0] !== 0.0) {
+                        x = intersection[1] / intersection[0];
+                        y = intersection[2] / intersection[0];
+                    } else {
+                        x = Infinity;
+                    }
+                }
+            } else { // Intersect with right border
+                border = [-box[2], 1, 0];
+                intersection = Mat.crossProduct(border, asymptote);
+                if (intersection[0] !== 0.0) {
+                    x = intersection[1] / intersection[0];
+                    y = intersection[2] / intersection[0];
+                } else {
+                    y = Infinity;
+                }
+
+                if (y < box[3]) { // Intersect with bottom border
+                    border = [-box[3], 0, 1];
+                    intersection = Mat.crossProduct(border, asymptote);
+                    if (intersection[0] !== 0.0) {
+                        x = intersection[1] / intersection[0];
+                        y = intersection[2] / intersection[0];
+                    } else {
+                        x = Infinity;
+                    }
+                } else if (y > box[1]) { // Intersect with top border
+                    border = [-box[1], 0, 1];
+                    intersection = Mat.crossProduct(border, asymptote);
+                    if (intersection[0] !== 0.0) {
+                        x = intersection[1] / intersection[0];
+                        y = intersection[2] / intersection[0];
+                    } else {
+                        x = Infinity;
+                    }
+                }
+            }
+            return [1, x, y];
+        },
+
+        /**
+         * 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 {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 (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,
+                 box,
+                 vx, vy, vx2, vy2, dx, dy,
+                 x, y,
+                 asymptote, border, intersection;
+
+
+             if (depth <= 1) {
+                pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.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.
+                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, [this.X(t, true), this.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.
+                // Usefule 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 = this.X(t_real, true) ;
+                vx2 = this.X(t_real2, true) ;
+                dx = (vx - vx2) / (t_real - t_real2);
+                vy = this.Y(t_real, true) ;
+                vy2 = this.Y(t_real2, true) ;
+                dy = (vy - vy2) / (t_real - t_real2);
+
+                // If the derivatives are large enough we draw the asymptote.
+                box = this.board.getBoundingBox();
+                if (Math.sqrt(dx * dx + dy * dy) > 500.0) {
+
+                    // The asymptote is a line of the form
+                    //  [c, a, b] = [dx * vy - dy * vx, dy, -dx]
+                    //  Now we have to find the intersection with the correct canvas border.
+                    asymptote = [dx * vy - dy * vx, dy, -dx];
+
+                    p_good = this._intersectWithBorder(asymptote, box, vx - vx2);
+                }
+
+                if (p_good !== null) {
+                    this._insertPoint(new Coords(Const.COORDS_BY_USER, p_good, this.board, false));
+                    return true;
+                }
+            }
+            return false;
+        },
+
+        /**
+         * 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 {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 (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], this.board, false);
+
+            for (i = 0; i < 20; ++i) {
+                t = ta + Math.random() * (tb - ta);
+                pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(t, true), this.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.
+         * @param  {Array}  a  Screen coordinates of the start point of the segment
+         * @param  {Array}  ta Curve parameter of a.
+         * @param  {Array}  b  Screen coordinates of the end point of the segment
+         * @param  {Array}  tb Curve parameter of b.
+         * @returns {Boolean}   True if the segment is too far away from the canvas, false otherwise.
+         */
+        _isOutside: function (a, ta, b, tb) {
+            var off = 500,
+                cw = this.board.canvasWidth,
+                ch = this.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));
+        },
+
+        /**
+         * Recursive interval bisection algorithm for curve plotting.
+         * Used in {@link JXG.Curve.updateParametricCurve}.
+         * @private
+         * @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: function (a, ta, b, tb, depth, delta) {
+            var tc, c,
+                ds, mindepth = 0,
+                isSmooth, isJump, isCusp,
+                cusp_threshold = 0.5,
+                pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false);
+
+            if (this.numberPoints > 65536) {
+                return;
+            }
+
+            // Test if the function is undefined on an interval
+            if (depth < this.nanLevel && this._isUndefined(a, ta, b, tb)) {
+                return this;
+            }
+
+            if (depth < this.nanLevel && this._isOutside(a, ta, b, tb)) {
+                return this;
+            }
+
+            tc = 0.5 * (ta  + tb);
+            pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(tc, true), this.Y(tc, true)], false);
+            c = pnt.scrCoords;
+
+            if (this._borderCase(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] > 0.99 * ds[0]) || (ds[1] > 0.99 * 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(new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board, false));
+            } else if (depth <= mindepth || isSmooth) {
+                this._insertPoint(pnt);
+                //if (this._borderCase(a, b, c, ta, tb, tc, depth)) {}
+            } else {
+                this._plotRecursive(a, ta, c, tc, depth, delta);
+                this._insertPoint(pnt);
+                this._plotRecursive(c, tc, b, tb, depth, delta);
+            }
+
+            return this;
+        },
+
+        /**
+         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is true.
+         * @param {Number} mi Left bound of curve
+         * @param {Number} ma Right bound of curve
+         * @returns {JXG.Curve} Reference to the curve object.
+         */
+        updateParametricCurve: function (mi, ma) {
+            var ta, tb, a, b,
+                suspendUpdate = false,
+                pa = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false),
+                pb = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false),
+                depth, delta;
+            if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) {
+                depth = 13;
+                delta = 2;
+                this.smoothLevel = depth - 7;
+                this.jumpLevel = 5;
+            } else {
+                depth = 17;
+                delta = 2;
+                this.smoothLevel = depth - 7; // 9
+                this.jumpLevel = 3;
+            }
+            this.nanLevel = depth - 4;
+
+            this.points = [];
+
+            ta = mi;
+            pa.setCoordinates(Const.COORDS_BY_USER, [this.X(ta, suspendUpdate), this.Y(ta, suspendUpdate)], false);
+            a = pa.copy('scrCoords');
+            suspendUpdate = true;
+
+            tb = ma;
+            pb.setCoordinates(Const.COORDS_BY_USER, [this.X(tb, suspendUpdate), this.Y(tb, suspendUpdate)], false);
+            b = pb.copy('scrCoords');
+
+            this.points.push(pa);
+            this._lastCrds = pa.copy('scrCoords');   //Used in _insertPoint
+            this._plotRecursive(a, ta, b, tb, depth, delta);
+            this.points.push(pb);
+
+            this.numberPoints = this.points.length;
+
+            return this;
+        },
+
+        /**
+         * Applies the transformations of the curve to the given point p.
+         * 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[1], c[2]], 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;
+
+            return function (t, suspendedUpdate) {
+                var i, j, f1, f2, z, t0, t1,
+                    arr = this[data],
+                    len = arr.length,
+                    f = [];
+
+                if (isNaN(t)) {
+                    return NaN;
+                }
+
+                if (t < 0) {
+                    if (Type.isFunction(arr[0])) {
+                        return arr[0]();
+                    }
+
+                    return arr[0];
+                }
+
+                if (this.bezierDegree === 3) {
+                    len /= 3;
+                    if (t >= len) {
+                        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 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('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('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;
+
+            // Read dependencies found by the JessieCode parser
+            for (fstr in {'xterm': 1, 'yterm': 1}) {
+                if (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);
+
+            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;
+
+            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]);
+                }
+                var bezier = Numerics.bezier(this.points);
+                var 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;
+        }
+    });
+
+
+    /**
+     * @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].
+     * 

+ * The following types of curves can be plotted: + *

    + *
  • parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. + *
  • polar curves: curves commonly written with polar equations like spirals and cardioids. + *
  • data plots: plot linbe segments through a given list of coordinates. + *
+ * @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. + *

+ * 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. + *

+ *

+ * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function + * returning this number. + *

+ *

+ * 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. + *

+ *

+ * Default values are a=-10 and b=10. + *

+ * @param {array_array,function,number} x,y Parent elements for Data Plots. + *

+ * 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. + *

+ * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves. + *

+ * The first parameter is a function term r(phi) describing the polar curve. + *

+ *

+ * 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]. + *

+ *

+ * 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. + *

+ *

+ * Default values are a=-10 and b=10. + *

+ * @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] + * ); + *
+ *
+     * @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});
+     * 
+ *
+     * @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]
+     *                     );
+     * 
+ *
+     *
+     * @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);
+     * 
+ *
+     *
+     *
+     */
+    JXG.createCurve = function (board, parents, attributes) {
+        var 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.
+     *         

+ * Further, an optional number or function for the left interval border a, + * and an optional number or function for the right interval border b. + *

+ * 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] + * ); + *

+ *
+     * @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();}]
+     *                     );
+     * 
+ *
+     */
+    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
+     *   
    + *
  • an array of JXGGraph points
  • + *
  • an array of coordinate pairs
  • + *
  • an array of functions returning coordinate pairs
  • + *
  • an array consisting of an array with x-coordinates and an array of y-coordinates
  • + *
+ * All individual entries of coordinates arrays may be numbers or functions returing 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}); + *
+ *
+     *
+     */
+    JXG.createSpline = function (board, parents, attributes) {
+        var el, f;
+
+        f = function () {
+            var D, x = [], y = [];
+
+            return function (t, suspended) {
+                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 point
+                    // 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);
+            };
+        };
+
+        attributes = Type.copyAttributes(attributes, board.options, 'curve');
+        attributes.curvetype = 'functiongraph';
+        el = new JXG.Curve(board, ['x', 'x', f()], 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 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.
+     *         

+ * n determines the number of bars, it is either a fixed number or a function. + *

+ * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezodial'. + * Default value is 'left'. + *

+ * 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. + *

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

+ *
+     *
+     * @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); }]);
+     * 
+ *
+     */
+    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;
+        c.Value = function () {
+            return this.sum;
+        };
+
+        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]);
+     *
+     * 
+ *
+     */
+    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);
+
+        c.updateDataArray = function () {
+            var i, step, t, el, pEl, x, y, v, 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]]);
+
+     * 
+ *
+     */
+    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);
+        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);
+
+    return {
+        Curve: JXG.Curve,
+        createCurve: JXG.createCurve,
+        createFunctiongraph: JXG.createFunctiongraph,
+        createPlot: JXG.createPlot,
+        createSpline: JXG.createSpline,
+        createRiemannsum: JXG.createRiemannsum,
+        createTracecurve: JXG.createTracecurve,
+        createStepfunction: JXG.createStepfunction
+    };
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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: 
    + *
  • {@link Arrowparallel} (currently private)
  • + *
  • {@link Bisector}
  • + *
  • {@link Msector}
  • + *
  • {@link Circumcircle}
  • + *
  • {@link Circumcirclemidpoint}
  • + *
  • {@link Integral}
  • + *
  • {@link Midpoint}
  • + *
  • {@link Mirrorpoint}
  • + *
  • {@link Normal}
  • + *
  • {@link Orthogonalprojection}
  • + *
  • {@link Parallel}
  • + *
  • {@link Perpendicular}
  • + *
  • {@link Perpendicularpoint}
  • + *
  • {@link Perpendicularsegment}
  • + *
  • {@link Reflection}
+ */ + +define('element/composition',[ + 'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/coords', 'utils/type', 'base/constants', + 'base/point', 'base/line', 'base/circle', 'base/transformation', 'base/composition', 'base/curve', 'base/text' +], function (JXG, Mat, Geometry, Numerics, Statistics, Coords, Type, Const, Point, Line, Circle, Transform, Composition, Curve, Text) { + + "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]); + *
+ *
+     */
+    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);
+
+        p.addChild(t);
+        l.addChild(t);
+
+        t.elType = 'orthogonalprojection';
+        t.setParents([p.id, t.id]);
+
+        t.update();
+
+        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]);
+     * 
+ *
+     */
+    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]);
+
+        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]);
+     * 
+ *
+     */
+    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);
+
+        p.addChild(t);
+        l.addChild(t);
+
+        t.elType = 'perpendicularpoint';
+        t.setParents([p.id, l.id]);
+
+        t.update();
+
+        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]);
+     * 
+ *
+     */
+    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;
+
+        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]);
+     * 
+ *
+     */
+    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);
+        a.addChild(t);
+        b.addChild(t);
+
+        t.elType = 'midpoint';
+        t.setParents([a.id, b.id]);
+
+        t.prepareUpdate().update();
+
+        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 v=p2-p1 the parallel point is determined by
+     * p4 = p3+v
+     * @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]);
+     * 
+ *
+     */
+    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
+        a.addChild(p);
+        b.addChild(p);
+        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]);
+     * 
+ *
+     */
+    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');
+        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);
+        }
+
+        /**
+         * 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 true. 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]);
+     * 
+ *
+     */
+    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 Glider#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]);
+     * 
+ *
+     */
+    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 undefined.
+             * @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;
+
+                        if (i === c.numberPoints - 1) {
+                            i -= 1;
+                            lbda = 1;
+                        }
+
+                        if (i < 0) {
+                            return 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));
+                    },
+                    function () {
+                        var i = Math.floor(p.position);
+
+                        if (i === c.numberPoints - 1) {
+                            i -= 1;
+                        }
+
+                        if (i < 0) {
+                            return 0;
+                        }
+
+                        return c.X(i + 1) - c.X(i);
+                    },
+                    function () {
+                        var i = Math.floor(p.position);
+
+                        if (i === c.numberPoints - 1) {
+                            i -= 1;
+                        }
+
+                        if (i < 0) {
+                            return 0;
+                        }
+
+                        return c.Y(i + 1) - c.Y(i);
+                    }
+                ], 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);
+
+        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 p1, p2 and p3 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]);
+     * 
+ *
+     */
+    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
+                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 l1 and l2 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]);
+     * 
+ *
+     */
+    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 p1, p2 and p3 will
+    //  * be divided into two angles according to the value of m.
+    //  * @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);
+    //  * 
+ // *
+    //  */
+    // 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]);
+     * 
+ *
+     */
+    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++) {
+                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]);
+     * 
+ *
+     */
+    JXG.createIncenter = function (board, parents, attributes) {
+        var p, A, B, C;
+
+        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);
+
+            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 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('circumcircle', [p1, p2, p3]);
+     * 
+ *
+     */
+    JXG.createCircumcircle = function (board, parents, attributes) {
+        var 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, '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);
+        } 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
+     * 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('incircle', [p1, p2, p3]);
+     * 
+ *
+     */
+    JXG.createIncircle = function (board, parents, attributes) {
+        var 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);
+
+            /**
+             * 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 a reflected point.
+     * @pseudo
+     * @description A reflected point is given by a point and a line. It is determined by the reflection of the given point
+     * against the given line.
+     * @constructor
+     * @name Reflection
+     * @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.Line} p,l The reflection point is the reflection of p against 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]);
+     * 
+ *
+     */
+    JXG.createReflection = function (board, parents, attributes) {
+        var l, p, r, t, i;
+
+        for (i = 0; i < parents.length; ++i) {
+            parents[i] = board.select(parents[i]);
+        }
+        if (Type.isPoint(parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
+            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
+            l = parents[1];
+        } else if (Type.isPoint(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 reflection point with parent types '" +
+                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
+                "\nPossible parent types: [line,point]");
+        }
+
+        t = Transform.createTransform(board, [l], {type: 'reflect'});
+        r = Point.createPoint(board, [p, t], attributes);
+        p.addChild(r);
+        l.addChild(r);
+
+        r.elType = 'reflection';
+        r.setParents(parents);
+
+        r.prepareUpdate().update();
+
+        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 = p.symbolic.x,
+                p2 = p.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 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.
+     * @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]);
+     * 
+ *
+     */
+    JXG.createMirrorPoint = function (board, parents, attributes) {
+        var p, i;
+
+        parents = Type.providePoints(board, parents, attributes, 'point');
+        if (Type.isPoint(parents[0]) && Type.isPoint(parents[1])) {
+            p = Point.createPoint(board, [
+                function () {
+                    return Geometry.rotation(parents[0], parents[1], Math.PI, board);
+                }
+            ], attributes);
+
+            for (i = 0; i < 2; i++) {
+                parents[i].addChild(p);
+            }
+
+            p.elType = 'mirrorpoint';
+            p.setParents(parents);
+
+        } else {
+            throw new Error("JSXGraph: Can't create mirror point with parent types '" +
+                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
+                "\nPossible parent types: [point,point]");
+        }
+
+        p.prepareUpdate().update();
+
+        return p;
+    };
+
+    /**
+     * @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 c and the x-axis
+     * within the interval i.
+     * @example
+     * var c1 = board.create('functiongraph', [function (t) { return t*t*t; }]);
+     * var i1 = board.create('integral', [[-1.0, 4.0], c1]);
+     * 
+ *
+     */
+    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 '∫ = ' + 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);
+        }
+
+        /** @ignore */
+        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.
+         * @memberOf Integral.prototype
+         * @name baseLeft
+         * @type JXG.Point
+         */
+        p.baseLeft = pa_on_axis;
+
+        /**
+         * The point on the axis initially corresponding to the higher value of the interval.
+         * @memberOf Integral.prototype
+         * @name baseRight
+         * @type JXG.Point
+         */
+        p.baseRight = pb_on_axis;
+
+        /**
+         * The glider on the curve corresponding to the lower value of the interval.
+         * @memberOf Integral.prototype
+         * @name curveLeft
+         * @type Glider
+         */
+        p.curveLeft = pa_on_curve;
+
+        /**
+         * The glider on the axis corresponding to the higher value of the interval.
+         * @memberOf Integral.prototype
+         * @name curveRight
+         * @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. It uses options given in {@link JXG.Options#grid}. This method does not
+     * take any parent elements. It is usually instantiated on the board's creation via the attribute grid 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', []);
+     * 
+ *
+     */
+    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;
+
+        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.
+     * @pseudo
+     * @description Display the solution set of a linear inequality (less than or equal to).
+     * To be precise, the solution set of the inequality y <= b/a * x + c/a is shown.
+     * In case a = 0, that is if the equation of the line is bx + c = 0,
+     * the area of the inequality bx + c <= 0 is shown.
+     * @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]);
+     * 
+ *
+     *
+     * @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});
+     * 
+ *
+     */
+    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)
+                    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 = 2* 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 {
+            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]");
+            }
+        }
+
+        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('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,
+        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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'parser/geonext', 'utils/color', 'utils/type', 'utils/event', 'utils/env', 'base/transformation',
+    'base/point', 'base/line', 'base/text', 'element/composition', 'base/composition'
+], function (JXG, Const, Coords, Options, Numerics, Mat, Geometry, Complex, Statistics, JessieCode, GeonextParser, Color, Type,
+                EventEmitter, Env, Transform, Point, Line, Text, Composition, EComposition) {
+
+    '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
+         * {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 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;
+
+        /**
+         * 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;
+
+        /**
+         * 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 (typeof 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
+         */
+        this.renderer = renderer;
+
+        /**
+         * Grids keeps track of all grids attached to this board.
+         */
+        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
+         */
+        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().
+         * @type Number
+         */
+        this.zoomX = zoomX;
+
+        /**
+         * Zoom factor in Y direction. It only stores the zoom factor to be able
+         * to get back to 100% in zoom100().
+         * @type Number
+         */
+        this.zoomY = zoomY;
+
+        /**
+         * The number of pixels which represent one unit in user-coordinates in x direction.
+         * @type Number
+         */
+        this.unitX = unitX * this.zoomX;
+
+        /**
+         * The number of pixels which represent one unit in user-coordinates in y direction.
+         * @type Number
+         */
+        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.
+         */
+        this.keepaspectratio = false;
+
+        /**
+         * Canvas width.
+         * @type Number
+         */
+        this.canvasWidth = canvasWidth;
+
+        /**
+         * Canvas Height
+         * @type Number
+         */
+        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
+         * 
    + *
  • JXG.Board.BOARD_MODE_NONE
  • + *
  • JXG.Board.BOARD_MODE_DRAG
  • + *
  • JXG.Board.BOARD_MODE_MOVE_ORIGIN
  • + *
+ * @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 + *
    + *
  • BOARD_QUALITY_LOW
  • + *
  • BOARD_QUALITY_HIGH
  • + *
+ * @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 {@link 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 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]; + + 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 bounding box for the selection + * @type {Array} + * @default [ [0,0], [0,0] ] + */ + this.selectingBox = [[0, 0], [0, 0]]; + + 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 = ['', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', + 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', + 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω']; + } 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 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 + * + **********************************************************/ + + /** + * 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 user coordinates, ready for {@link JXG.Coords} + */ + getMousePosition: function (e, i) { + var cPos = this.getCoordsTopLeftCorner(), + absPos, + v; + + // position of mouse cursor relative to containers position of container + absPos = Env.getPosition(e, i, this.document); + + /** + * In case there has been no down event before. + */ + if (!Type.exists(this.cssTransMat)) { + this.updateCSSTransforms(); + } + 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: + *
  • isDraggable
  • visible
  • not fixed
  • not frozen
+ * @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' or 'mouse'. + * @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 (((this.geonextCompatibilityMode && + (Type.isPoint(pEl) || + pEl.elementClass === Const.OBJECT_CLASS_TEXT)) || + !this.geonextCompatibilityMode) && + pEl.isDraggable && + pEl.visPropCalc.visible && + (!Type.evaluate(pEl.visProp.fixed)) && /*(!pEl.visProp.frozen) &&*/ + haspoint) { + // 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 (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); + } + + 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.showInfobox(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 + 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); + + 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 (p1, p2, o, evt) { + var np1c, np2c, drag; + if (Type.exists(o) && Type.exists(o.obj)) { + drag = o.obj; + } else { + return; + } + + // New finger position + np1c = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p1[0], p1[1]), this); + np2c = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p2[0], p2[1]), this); + + if (drag.elementClass === Const.OBJECT_CLASS_LINE || + drag.type === Const.OBJECT_TYPE_POLYGON) { + this.twoFingerTouchObject(np1c, np2c, o, drag); + } else if (drag.elementClass === Const.OBJECT_CLASS_CIRCLE) { + this.twoFingerTouchCircle(np1c, np2c, o, drag); + } + drag.triggerEventHandlers(['touchdrag', 'drag'], [evt]); + + o.targets[0].Xprev = np1c.scrCoords[1]; + o.targets[0].Yprev = np1c.scrCoords[2]; + o.targets[1].Xprev = np2c.scrCoords[1]; + o.targets[1].Yprev = np2c.scrCoords[2]; + }, + + /** + * Moves a line or polygon with two fingers + * @param {JXG.Coords} np1c x,y coordinates of first touch + * @param {JXG.Coords} np2c x,y coordinates of second touch + * @param {object} o The touch object that is dragged: {JXG.Board#touches}. + * @param {object} drag The object that is dragged: + */ + twoFingerTouchObject: function (np1c, np2c, o, drag) { + var np1, np2, op1, op2, + nmid, omid, nd, od, + d, + S, alpha, t1, t2, t3, t4, t5, + ar, i, len; + + if (Type.exists(o.targets[0]) && + Type.exists(o.targets[1]) && + !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) { + + np1 = np1c.usrCoords; + np2 = np2c.usrCoords; + // Previous finger position + op1 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[0].Xprev, o.targets[0].Yprev], this)).usrCoords; + op2 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[1].Xprev, o.targets[1].Yprev], this)).usrCoords; + + // Affine mid points of the old and new positions + omid = [1, (op1[1] + op2[1]) * 0.5, (op1[2] + op2[2]) * 0.5]; + nmid = [1, (np1[1] + np2[1]) * 0.5, (np1[2] + np2[2]) * 0.5]; + + // Old and new directions + od = Mat.crossProduct(op1, op2); + nd = Mat.crossProduct(np1, np2); + S = Mat.crossProduct(od, nd); + + // If parallel, translate otherwise rotate + if (Math.abs(S[0]) < Mat.eps) { + return; + } + + S[1] /= S[0]; + S[2] /= S[0]; + alpha = Geometry.rad(omid.slice(1), S.slice(1), nmid.slice(1)); + t1 = this.create('transform', [alpha, S[1], S[2]], {type: 'rotate'}); + + // Old midpoint of fingers after first transformation: + t1.update(); + omid = Mat.matVecMult(t1.matrix, omid); + omid[1] /= omid[0]; + omid[2] /= omid[0]; + + // Shift to the new mid point + t2 = this.create('transform', [nmid[1] - omid[1], nmid[2] - omid[2]], {type: 'translate'}); + t2.update(); + //omid = Mat.matVecMult(t2.matrix, omid); + + t1.melt(t2); + if (Type.evaluate(drag.visProp.scalable)) { + // Scale + d = Geometry.distance(np1, np2) / Geometry.distance(op1, op2); + t3 = this.create('transform', [-nmid[1], -nmid[2]], {type: 'translate'}); + t4 = this.create('transform', [d, d], {type: 'scale'}); + t5 = this.create('transform', [nmid[1], nmid[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 a circle with two fingers + * @param {JXG.Coords} np1c x,y coordinates of first touch + * @param {JXG.Coords} np2c x,y coordinates of second touch + * @param {object} o The touch object that is dragged: {JXG.Board#touches}. + * @param {object} drag The object that is dragged: + */ + twoFingerTouchCircle: function (np1c, np2c, o, drag) { + var np1, np2, op1, op2, + d, alpha, t1, t2, t3, t4, t5; + + if (drag.method === 'pointCircle' || + drag.method === 'pointLine') { + return; + } + + if (Type.exists(o.targets[0]) && + Type.exists(o.targets[1]) && + !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) { + + np1 = np1c.usrCoords; + np2 = np2c.usrCoords; + // Previous finger position + op1 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[0].Xprev, o.targets[0].Yprev], this)).usrCoords; + op2 = (new Coords(Const.COORDS_BY_SCREEN, [o.targets[1].Xprev, o.targets[1].Yprev], this)).usrCoords; + + // Shift by the movement of the first finger + t1 = this.create('transform', [np1[1] - op1[1], np1[2] - op1[2]], {type: 'translate'}); + alpha = Geometry.rad(op2.slice(1), np1.slice(1), np2.slice(1)); + + // Rotate and scale by the movement of the second finger + t2 = this.create('transform', [-np1[1], -np1[2]], {type: 'translate'}); + t3 = this.create('transform', [alpha], {type: 'rotate'}); + t1.melt(t2).melt(t3); + + if (Type.evaluate(drag.visProp.scalable)) { + d = Geometry.distance(np1, np2) / Geometry.distance(op1, op2); + t4 = this.create('transform', [d, d], {type: 'scale'}); + t1.melt(t4); + } + t5 = this.create('transform', [np1[1], np1[2]], {type: 'translate'}); + t1.melt(t5); + + 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(); + 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; + for (i = 0; i < len; i++) { + xy.push(this.select(obj.parents[i]).coords.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. + */ + touchOriginMoveStart: function (evt) { + var touches = evt[JXG.touchProperty], + r, pos; + + r = this.attr.pan.enabled && + !this.attr.pan.needtwofingers && + touches.length == 1; + + if (r) { + pos = this.getMousePosition(evt, 0); + this.initMoveOrigin(pos[0], pos[1]); + } + + return r; + }, + + /** + * 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(); + } + //if (Env.isBrowser) { + //Env.addEvent(window, 'resize', this.update, this); + //} + + // 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; + }; + } + + }, + + /** + * Registers the MSPointer* event handlers. + */ + addPointerEventHandlers: function () { + if (!this.hasPointerHandlers && Env.isBrowser) { + if (window.navigator.pointerEnabled) { // IE11+ + Env.addEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this); + Env.addEvent(this.containerObj, 'pointermove', this.pointerMoveListener, this); + } else { + Env.addEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this); + Env.addEvent(this.containerObj, 'MSPointerMove', 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) { + Env.addEvent(this.containerObj, 'mousedown', this.mouseDownListener, this); + Env.addEvent(this.containerObj, '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. + */ + addTouchEventHandlers: function (appleGestures) { + if (!this.hasTouchHandlers && Env.isBrowser) { + Env.addEvent(this.containerObj, 'touchstart', this.touchStartListener, this); + Env.addEvent(this.containerObj, '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; + } + }, + + /** + * Remove MSPointer* Event handlers. + */ + removePointerEventHandlers: function () { + if (this.hasPointerHandlers && Env.isBrowser) { + if (window.navigator.pointerEnabled) { // IE11+ + Env.removeEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this); + Env.removeEvent(this.containerObj, 'pointermove', this.pointerMoveListener, this); + } else { + Env.removeEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this); + Env.removeEvent(this.containerObj, 'MSPointerMove', 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.pointerEnabled) { // IE11+ + Env.removeEvent(this.document, 'pointerup', this.pointerUpListener, this); + } else { + Env.removeEvent(this.document, 'MSPointerUp', this.pointerUpListener, this); + } + this.hasPointerUp = false; + } + + this.hasPointerHandlers = false; + } + }, + + /** + * De-register mouse event handlers. + */ + removeMouseEventHandlers: function () { + if (this.hasMouseHandlers && Env.isBrowser) { + Env.removeEvent(this.containerObj, 'mousedown', this.mouseDownListener, this); + Env.removeEvent(this.containerObj, '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) { + Env.removeEvent(this.containerObj, 'touchstart', this.touchStartListener, this); + Env.removeEvent(this.containerObj, 'touchmove', this.touchMoveListener, this); + + if (this.hasTouchEnd) { + Env.removeEvent(this.document, 'touchend', this.touchEndListener, this); + this.hasTouchEnd = false; + } + + this.hasTouchHandlers = false; + } + }, + + /** + * Remove all event handlers from the board object + */ + removeEventHandlers: function () { + this.removeMouseEventHandlers(); + this.removeTouchEventHandlers(); + this.removePointerEventHandlers(); + }, + + /** + * 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, + // 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(); + + c = new Coords(Const.COORDS_BY_SCREEN, this.getMousePosition(evt, 0), this); + dist = Geometry.distance([evt.touches[0].clientX, evt.touches[0].clientY], + [evt.touches[1].clientX, evt.touches[1].clientY], 2); + + // Android pinch to zoom + if (evt.scale === undefined) { + // evt.scale is undefined in Android + evt.scale = dist / this.prevDist; + } + + factor = evt.scale / this.prevScale; + this.prevScale = evt.scale; + + // pan detected + if (this.attr.pan.enabled && + this.attr.pan.needtwofingers && + Math.abs(evt.scale - 1.0) < 0.4 && + this._num_pan >= 0.8 * this._num_zoom) { + + this._num_pan++; + this.moveOrigin(c.scrCoords[1], c.scrCoords[2], true); + } else if (this.attr.zoom.enabled && + Math.abs(factor - 1.0) < 0.5) { + + this._num_zoom++; + + 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); + + // 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._num_zoom = this._num_pan = 0; + 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 + */ + + _pointerAddBoardTouches: 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; + } + } + + if (!found) { + this._board_touches.push({ + pointerId: evt.pointerId, + clientX: evt.clientX, + clientY: evt.clientY + }); + } + + return this; + }, + + _pointerRemoveBoardTouches: 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; + }, + + /** + * 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, + eps = this.options.precision.touch, + found, target, result; + + if (!this.hasPointerUp) { + if (window.navigator.pointerEnabled) { // IE11+ + Env.addEvent(this.document, 'pointerup', this.pointerUpListener, this); + } else { + Env.addEvent(this.document, 'MSPointerUp', 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) {} + } + } + + // Touch or pen device + if (Env.isBrowser && + (evt.pointerType === 'touch' || // New + (window.navigator.msMaxTouchPoints && window.navigator.msMaxTouchPoints > 1)) // Old + ) { + this.options.precision.hasPoint = eps; + } + + // This should be easier than the touch events. Every pointer device gets its own pointerId, e.g. the mouse + // always has id 1, 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 (object) { + elements = [ object ]; + this.mode = this.BOARD_MODE_DRAG; + } else { + elements = this.initMoveObject(pos[0], pos[1], evt, 'mouse'); + } + + // 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; + for (i = 0; i < this.touches.length; i++) { + // the target is already in our touches array, try to add the pointer to the existing touch + if (this.touches[i].obj === target) { + j = i; + k = this.touches[i].targets.push({ + num: evt.pointerId, + X: pos[0], + Y: pos[1], + Xprev: NaN, + Yprev: NaN, + Xstart: [], + Ystart: [], + Zstart: [] + }) - 1; + + found = true; + break; + } + } + + if (!found) { + k = 0; + j = this.touches.push({ + obj: target, + targets: [{ + num: evt.pointerId, + X: pos[0], + Y: pos[1], + Xprev: NaN, + Yprev: NaN, + Xstart: [], + Ystart: [], + Zstart: [] + }] + }) - 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(); + } + + this.options.precision.hasPoint = this.options.precision.mouse; + + if (Env.isBrowser && evt.pointerType !== 'touch') { + if (this.mode === this.BOARD_MODE_NONE) { + this.mouseOriginMoveStart(evt); + } + } else { + this._pointerAddBoardTouches(evt); + evt.touches = this._board_touches; + + // See touchStartListener + if (this.mode === this.BOARD_MODE_NONE && this.touchOriginMoveStart(evt)) { + } else if ((this.mode === this.BOARD_MODE_NONE || + this.mode === this.BOARD_MODE_MOVE_ORIGIN) && + evt.touches.length == 2) { + if (this.mode === this.BOARD_MODE_MOVE_ORIGIN) { + this.originMoveEnd(); + } + + this.gestureStartListener(evt); + } + } + + this.triggerEventHandlers(['touchstart', 'down', 'pointerdown', 'MSPointerDown'], [evt]); + + //return result; + return false; + }, + + /** + * 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; + + if (this.mode !== this.BOARD_MODE_DRAG) { + this.dehighlightAll(); + this.showInfobox(false); + } + + if (this.mode !== this.BOARD_MODE_NONE) { + evt.preventDefault(); + evt.stopPropagation(); + } + + // Touch or pen device + if (Env.isBrowser && + (evt.pointerType === 'touch' || // New + (window.navigator.msMaxTouchPoints && window.navigator.msMaxTouchPoints > 1)) // Old + ) { + this.options.precision.hasPoint = this.options.precision.touch; + } + this.updateQuality = this.BOARD_QUALITY_LOW; + + // 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) { + // Runs through all elements which are touched by at least one finger. + for (i = 0; i < this.touches.length; i++) { + for (j = 0; j < this.touches[i].targets.length; j++) { + if (this.touches[i].targets[j].num === evt.pointerId) { + // Touch by one finger: this is possible for all elements that can be dragged + if (this.touches[i].targets.length === 1) { + this.touches[i].targets[j].X = evt.pageX; + this.touches[i].targets[j].Y = evt.pageY; + pos = this.getMousePosition(evt); + this.moveObject(pos[0], pos[1], this.touches[i], evt, 'touch'); + // Touch by two fingers: moving lines + } else if (this.touches[i].targets.length === 2 && + this.touches[i].targets[0].num > -1 && this.touches[i].targets[1].num > -1) { + + this.touches[i].targets[j].X = evt.pageX; + this.touches[i].targets[j].Y = evt.pageY; + + this.twoFingerMove( + this.getMousePosition({ + clientX: this.touches[i].targets[0].X, + clientY: this.touches[i].targets[0].Y + }), + this.getMousePosition({ + clientX: this.touches[i].targets[1].X, + clientY: this.touches[i].targets[1].Y + }), + this.touches[i], + evt + ); + } + + // there is only one pointer in the evt object, there's no point in looking further + break; + } + } + } + } else { + if (evt.pointerType == 'touch') { + this._pointerAddBoardTouches(evt); + if (this._board_touches.length == 2) { + evt.touches = this._board_touches; + this.gestureChangeListener(evt); + } + } else { + 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.showInfobox(false); + //} + + this.options.precision.hasPoint = this.options.precision.mouse; + this.triggerEventHandlers(['touchmove', 'move', 'pointermove', 'MSPointerMove'], [evt, this.mode]); + + 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; + + this.triggerEventHandlers(['touchend', 'up', 'pointerup', 'MSPointerUp'], [evt]); + this.showInfobox(false); + + if (evt) { + for (i = 0; i < this.touches.length; i++) { + for (j = 0; j < this.touches[i].targets.length; j++) { + if (this.touches[i].targets[j].num === evt.pointerId) { + this.touches[i].targets.splice(j, 1); + + if (this.touches[i].targets.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._pointerRemoveBoardTouches(evt); + + // if (this.touches.length === 0) { + if (this._board_touches.length === 0) { + if (this.hasPointerUp) { + if (window.navigator.pointerEnabled) { // IE11+ + Env.removeEvent(this.document, 'pointerup', this.pointerUpListener, this); + } else { + Env.removeEvent(this.document, 'MSPointerUp', this.pointerUpListener, this); + } + this.hasPointerUp = false; + } + + this.dehighlightAll(); + this.updateQuality = this.BOARD_QUALITY_HIGH; + this.mode = this.BOARD_MODE_NONE; + + this.originMoveEnd(); + this.update(); + } + + + 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, time, + eps = this.options.precision.touch, + obj, found, targets, + evtTouches = evt[JXG.touchProperty], + target; + + 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.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++) { + for (j = 0; j < this.touches[i].targets.length; j++) { + this.touches[i].targets[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 - this.touches[i].targets[j].X, 2) + + Math.pow(evtTouches[k].screenY - this.touches[i].targets[j].Y, 2)) < eps * eps) { + this.touches[i].targets[j].num = k; + + this.touches[i].targets[j].X = evtTouches[k].screenX; + this.touches[i].targets[j].Y = evtTouches[k].screenY; + evtTouches[k].jxg_isused = true; + break; + } + } + + eps *= 2; + + } while (this.touches[i].targets[j].num === -1 && eps < this.options.precision.touchMax); + + if (this.touches[i].targets[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); + this.touches[i].targets.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]; + + 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 = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }]; + + // 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) { + target = { num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }; + + // 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 = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }]; + + // 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 (this.mode === this.BOARD_MODE_NONE && this.touchOriginMoveStart(evt)) { + } else if (evtTouches.length == 2 && + (this.mode === this.BOARD_MODE_NONE || + this.mode === this.BOARD_MODE_MOVE_ORIGIN /*|| + (this.mode === this.BOARD_MODE_DRAG && this.touches.length == 1) */ + )) { + // 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, time, + evtTouches = evt[JXG.touchProperty]; + + if (this.mode !== this.BOARD_MODE_NONE) { + evt.preventDefault(); + evt.stopPropagation(); + } + + // Reduce update frequency for Android devices + // if (false && Env.isWebkitAndroid()) { + // time = new Date(); + // time = time.getTime(); + // + // if (time - this.touchMoveLast < 80) { + // this.updateQuality = this.BOARD_QUALITY_HIGH; + // this.triggerEventHandlers(['touchmove', 'move'], [evt, this.mode]); + // + // return false; + // } + // + // this.touchMoveLast = time; + // } + + if (this.mode !== this.BOARD_MODE_DRAG) { + this.showInfobox(false); + } + + 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++) { + // Touch by one finger: this is possible for all elements that can be dragged + if (this.touches[i].targets.length === 1) { + if (evtTouches[this.touches[i].targets[0].num]) { + pos1 = this.getMousePosition(evt, this.touches[i].targets[0].num); + if (pos1[0] < 0 || pos1[0] > this.canvasWidth || pos1[1] < 0 || pos1[1] > this.canvasHeight) { + return; + } + this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX; + this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY; + this.moveObject(pos1[0], pos1[1], this.touches[i], evt, 'touch'); + } + // Touch by two fingers: moving lines + } else if (this.touches[i].targets.length === 2 && + this.touches[i].targets[0].num > -1 && + this.touches[i].targets[1].num > -1) { + if (evtTouches[this.touches[i].targets[0].num] && evtTouches[this.touches[i].targets[1].num]) { + pos1 = this.getMousePosition(evt, this.touches[i].targets[0].num); + pos2 = this.getMousePosition(evt, this.touches[i].targets[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; + } + this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX; + this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY; + this.touches[i].targets[1].X = evtTouches[this.touches[i].targets[1].num].screenX; + this.touches[i].targets[1].Y = evtTouches[this.touches[i].targets[1].num].screenY; + this.twoFingerMove(pos1, pos2, this.touches[i], evt); + } + } + } + } else { + if (evtTouches.length == 2) { + this.gestureChangeListener(evt); + } + } + } + } + + if (this.mode !== this.BOARD_MODE_DRAG) { + this.showInfobox(false); + } + + /* + this.updateQuality = this.BOARD_QUALITY_HIGH; is set in touchEnd + */ + this.options.precision.hasPoint = this.options.precision.mouse; + this.triggerEventHandlers(['touchmove', 'move'], [evt, this.mode]); + + 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]; + + this.triggerEventHandlers(['touchend', 'up'], [evt]); + this.showInfobox(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; + + for (j = 0; j < tmpTouches[i].targets.length; j++) { + tmpTouches[i].targets[j].found = false; + for (k = 0; k < evtTouches.length; k++) { + if (Math.abs(Math.pow(evtTouches[k].screenX - tmpTouches[i].targets[j].X, 2) + Math.pow(evtTouches[k].screenY - tmpTouches[i].targets[j].Y, 2)) < eps * eps) { + tmpTouches[i].targets[j].found = true; + tmpTouches[i].targets[j].num = k; + tmpTouches[i].targets[j].X = evtTouches[k].screenX; + tmpTouches[i].targets[j].Y = evtTouches[k].screenY; + foundNumber += 1; + break; + } + } + } + + if (Type.isPoint(tmpTouches[i].obj)) { + found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found); + } else if (tmpTouches[i].obj.elementClass === Const.OBJECT_CLASS_LINE) { + found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found) || (tmpTouches[i].targets[1] && tmpTouches[i].targets[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 < tmpTouches[i].targets.length; j++) { + if (tmpTouches[i].targets[j].found) { + this.touches[this.touches.length - 1].targets.push({ + num: tmpTouches[i].targets[j].num, + X: tmpTouches[i].targets[j].screenX, + Y: tmpTouches[i].targets[j].screenY, + Xprev: NaN, + Yprev: NaN, + Xstart: tmpTouches[i].targets[j].Xstart, + Ystart: tmpTouches[i].targets[j].Ystart, + Zstart: tmpTouches[i].targets[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; + } + + 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 { + 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; + + pos = this.getMousePosition(evt); + + this.updateQuality = this.BOARD_QUALITY_LOW; + + if (this.mode !== this.BOARD_MODE_DRAG) { + this.dehighlightAll(); + this.showInfobox(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 + 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 + 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; + }, + + /********************************************************** + * + * 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'; + 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.showInfobox(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 + */ + updateInfobox: function (el) { + var x, y, xc, yc, + vpinfoboxdigits; + + if (!Type.evaluate(el.visProp.showinfobox)) { + 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.showInfobox(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 + * @return {JXG.Board} Reference to the board. + */ + showInfobox: function(val) { + if (this.infobox.hiddenByParent == val) { + this.infobox.hiddenByParent = !val; + this.infobox.prepareUpdate().updateVisibility(val).updateRenderer(); + } + return this; + }, + + /** + * 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. + * @param {Number} x X coordinate in screen coordinates + * @param {Number} y Y coordinate in screen coordinates + * @returns {Array} Coordinates of the mouse in screen coordinates. + */ + 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 of the mouse in screen coordinates. + */ + 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. + */ + 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. + */ + 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) { + if (Type.exists(x) && Type.exists(y)) { + 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; + } + } + + 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(''), + 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 + 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 "" + i = str.indexOf(''); + 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)], false); + this.zoomX *= zX; + this.zoomY *= zY; + 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)], false); + this.zoomX /= zX; + this.zoomY /= zY; + + return this.applyZoom(); + }, + + /** + * Resets zoom factor to 100%. + * @returns {JXG.Board} Reference to the board + */ + zoom100: function () { + var 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], false); + this.zoomX = 1.0; + this.zoomY = 1.0; + 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.zoomX = 1.0; + this.zoomY = 1.0; + + this.setBoundingBox([minX - borderX, maxY + borderY, maxX + borderX, minY - borderY], true); + + 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, j, e, box, + newBBox = [0, 0, 0, 0], + dir = [1, -1, -1, 1]; + + 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 (Type.isArray(newBBox)) { + for (j = 0; j < 4; j++) { + if (dir[j] * box[j] < dir[j] * newBBox[j]) { + newBBox[j] = box[j]; + } + } + } else { + newBBox = box; + } + } + } + + if (Type.isArray(newBBox)) { + for (j = 0; j < 4; j++) { + newBBox[j] -= dir[j]; + } + + this.zoomX = 1.0; + this.zoomY = 1.0; + this.setBoundingBox(newBBox, true); + } + + return this; + }, + + /** + * Sets the zoom level to fX resp fY. + * @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. + * @param {JXG.GeometryElement} object The object to remove. + * @returns {JXG.Board} Reference to the board + */ + removeObject: function (object) { + 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]); + } + } + + for (el in this.objects) { + if (this.objects.hasOwnProperty(el) && Type.exists(this.objects[el].childElements)) { + delete this.objects[el].childElements[object.id]; + delete this.objects[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 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; + + if (!dontSetBoundingBox) { + box = this.getBoundingBox(); + } + this.canvasWidth = parseInt(canvasWidth, 10); + this.canvasHeight = parseInt(canvasHeight, 10); + + 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); + } + + 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 = '

\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 += '' + 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 += '

\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('

' + 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];
+                // 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.
+                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 mouseup, mousedown and update.
+         * @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;
+
+            if (this.inUpdate || this.isSuspendedUpdate) {
+                return this;
+            }
+            this.inUpdate = true;
+
+            if (this.attr.minimizereflow === 'all' && this.containerObj && this.renderer.type !== 'vml') {
+                insert = this.renderer.removeToInsertLater(this.containerObj);
+            }
+
+            if (this.attr.minimizereflow === 'svg' && this.renderer.type === 'svg') {
+                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();
+            }
+
+            // 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 === 'input' || elementType === 'checkbox' || elementType === 'button') &&
+                      (i === 2 || i == 3))
+                ) {
+                    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.
+         * @returns {JXG.Board} Reference to the board
+         */
+        setBoundingBox: function (bbox, keepaspectratio) {
+            var h, w,
+                dim = Env.getDimensions(this.container, this.document);
+
+            if (!Type.isArray(bbox)) {
+                return this;
+            }
+
+            this.plainBB = bbox;
+
+            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);
+                } else {
+                    this.unitX = Math.abs(this.unitY) * this.unitX / Math.abs(this.unitX);
+                }
+                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], this.unitY * bbox[1]);
+
+            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),
+                lr = new Coords(Const.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this);
+
+            return [ul.usrCoords[1], ul.usrCoords[2], lr.usrCoords[1], lr.usrCoords[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.
+         * @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) {
+            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 (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 EComposition(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 x 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 sclaing type. 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:
+         * 
+         * 	<button onclick="board.startSelectionMode()">Start</button>
+         * 
+ * @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); + * }); + * + * + *
+ *
+         *
+         */
+        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 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 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
+         */
+        __evt__hit: function (e, el, target) { },
+
+        /**
+         * @event
+         * @description Whenever an element is highlighted this event is fired.
+         * @name JXG.Board#mousehit
+         * @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
+
+        /**
+         * 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()
+         *
+         * 
+ *
+         */
+        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-2017
+        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 
+    and .
+ */
+
+/*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/env', 'utils/color', 'utils/base64', 'math/numerics'
+], function (JXG, Options, AbstractRenderer, Const, Type, Env, Color, Base64, Numerics) {
+
+    "use strict";
+
+    /**
+     * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
+     * @class JXG.AbstractRenderer
+     * @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.resize(dim.width, dim.height);
+
+        //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision');
+
+        this.container.appendChild(this.svgRoot);
+
+        /**
+         * The defs 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("www.http://w3.org/TR/SVG11/feature#Extensibility", "1.1");
+
+        if (this.supportsForeignObject) {
+            this.foreignObjLayer = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject');
+            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: 
    + *
  1. 2px dash, 2px space
  2. + *
  3. 5px dash, 5px space
  4. + *
  5. 10px dash, 10px space
  6. + *
  7. 20px dash, 20px space
  8. + *
  9. 20px dash, 10px space, 10px dash, 10px dash
  10. + *
  11. 20px dash, 5px space, 10px dash, 5px space
+ * @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 marker 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) { + var node2, node3, + id = el.id + 'Triangle', + type = null, + w, s, + ev_fa = Type.evaluate(el.visProp.firstarrow), + ev_la = Type.evaluate(el.visProp.lastarrow); + + 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'); + + /* + The arrow head is an isosceles triangle with base length 10 and height 10. + This 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(). + */ + node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path'); + if (idAppendix === 'End') { + // First arrow + if (JXG.exists(ev_fa.type)) { + type = Type.evaluate(ev_fa.type); + } + + node2.setAttributeNS(null, 'refY', 5); + if (type === 2) { + node2.setAttributeNS(null, 'refX', 4.9); + node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 L 5,5 z'); + } else if (type === 3) { + node2.setAttributeNS(null, 'refX', 3.33); + node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); + } else { + node2.setAttributeNS(null, 'refX', 9.9); + node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 z'); + } + } else { + // Last arrow + if (JXG.exists(ev_la.type)) { + type = Type.evaluate(ev_la.type); + } + + node2.setAttributeNS(null, 'refY', 5); + if (type === 2) { + node2.setAttributeNS(null, 'refX', 5.1); + node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 L 5,5 z'); + } else if (type === 3) { + node2.setAttributeNS(null, 'refX', 0.1); + node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z'); + } else { + node2.setAttributeNS(null, 'refX', 0.1); + node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 z'); + } + } + + 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. #00ff00 or green for green. + * @param {Number} opacity + * @param {JXG.GeometryElement} el The element the arrows are to be attached to + */ + _setArrowColor: function (node, color, opacity, el) { + var s, d; + + if (node) { + if (Type.isString(color)) { + 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); + } + + 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) { + 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, c, node, x, y, + tickStr = '', + 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])) { + tickStr += "M " + (x[0]) + " " + (y[0]) + " L " + (x[1]) + " " + (y[1]) + " "; + } + } + + 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, '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 = Type.evaluate(el.visProp.anchorx), + ev_ay = Type.evaluate(el.visProp.anchory); + + 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); + + el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url); + }, + + // 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); + }, + + /* ************************** + * 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'); + } + 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 + makeArrows: function (el) { + var node2, + 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) { + 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'); + 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'); + 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'); + + 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, col, op, + node, node2, node3, x1, x2, y1, y2, + ev_g = Type.evaluate(el.visProp.gradient); + + op = Type.evaluate(el.visProp.fillopacity); + op = (op > 0) ? op : 0; + col = Type.evaluate(el.visProp.fillcolor); + + if (ev_g === 'linear') { + node = this.createPrim('linearGradient', el.id + '_gradient'); + x1 = '0%'; + x2 = '100%'; + y1 = '0%'; + y2 = '0%'; + + node.setAttributeNS(null, 'x1', x1); + node.setAttributeNS(null, 'x2', x2); + node.setAttributeNS(null, 'y1', y1); + node.setAttributeNS(null, 'y2', y2); + node2 = this.createPrim('stop', el.id + '_gradient1'); + node2.setAttributeNS(null, 'offset', '0%'); + node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); + node3 = this.createPrim('stop', el.id + '_gradient2'); + node3.setAttributeNS(null, 'offset', '100%'); + node3.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + + ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); + 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; + } else if (ev_g === 'radial') { + node = this.createPrim('radialGradient', el.id + '_gradient'); + + node.setAttributeNS(null, 'cx', '50%'); + node.setAttributeNS(null, 'cy', '50%'); + node.setAttributeNS(null, 'r', '50%'); + node.setAttributeNS(null, 'fx', Type.evaluate(el.visProp.gradientpositionx) * 100 + '%'); + node.setAttributeNS(null, 'fy', Type.evaluate(el.visProp.gradientpositiony) * 100 + '%'); + + node2 = this.createPrim('stop', el.id + '_gradient1'); + node2.setAttributeNS(null, 'offset', '0%'); + node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + + ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); + node3 = this.createPrim('stop', el.id + '_gradient2'); + node3.setAttributeNS(null, 'offset', '100%'); + node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); + + 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; + } else { + fillNode.removeAttributeNS(null, 'style'); + } + }, + + // 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); + + if (ev_g === 'linear') { + 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)); + } else if (ev_g === 'radial') { + node2.setAttributeNS(null, 'style', 'stop-color:' + Type.evaluate(el.visProp.gradientsecondcolor) + + ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)); + node3.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op); + } + }, + + // 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 buit avoid transistions + * 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 { + setTimeout(setFunc, 1); + } + }, + + // documented in JXG.AbstractRenderer + setObjectFillColor: function (el, color, opacity, rendNode) { + var node, c, rgbo, oo, t, + rgba = Type.evaluate(color), + o = Type.evaluate(opacity); + + o = (o > 0) ? o : 0; + + if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === 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]; + } + + 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._setAttribute(function() { + node.setAttributeNS(null, 'fill-opacity', oo); + }, el.visPropOld.fillopacity); + } + + if (Type.exists(el.visProp.gradient)) { + 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); + } + + if (Type.evaluate(el.visProp.lastarrow)) { + this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el); + } + } + } + + el.visPropOld.strokecolor = rgba; + el.visPropOld.strokeopacity = o; + }, + + // documented in JXG.AbstractRenderer + setObjectStrokeWidth: function (el, width) { + var node, + w = Type.evaluate(width), + rgba, c, rgbo, o, oo; + + 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)); + }, + + // 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); + } + }, + + /** + * Convert the SVG construction into an HTML canvas image. + * This works for all SVG supporting browsers. + * For IE it works from version 9. + * But HTML texts are ignored on IE. The drawing is done with a delay of + * 200 ms. Otherwise there are 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. + * @returns {Object} the svg renderer object. + * + * @example + * board.renderer.dumpToCanvas('canvas'); + */ + dumpToCanvas: function(canvasId, w, h) { + var svgRoot = this.svgRoot, + btoa = window.btoa || Base64.encode, + svg, tmpImg, cv, ctx, + wOrg, hOrg, + uriPayload; + + // Move all HTML tags (beside the SVG root) of the container + // to the foreignObject element inside of the svgRoot node + if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) { + while (svgRoot.nextSibling) { + this.foreignObjLayer.appendChild(svgRoot.nextSibling); + } + } + + svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + wOrg = svgRoot.getAttribute('width'); + hOrg = svgRoot.getAttribute('height'); + + svg = new XMLSerializer().serializeToString(svgRoot); + + // 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 " " + svg = svg.replace(/ /g, ' '); + + cv = document.getElementById(canvasId); + // Clear the canvas + cv.width = cv.width; + + ctx = cv.getContext("2d"); + if (w !== undefined && h !== undefined) { + // Scale twice the CSS size to make the image crisp + cv.style.width = parseFloat(w) + 'px'; + cv.style.height = parseFloat(h) + 'px'; + cv.setAttribute('width', 2 * parseFloat(wOrg)); + cv.setAttribute('height', 2 * parseFloat(hOrg)); + ctx.scale(2 * wOrg / w, 2 * hOrg / h); + } + + tmpImg = new Image(); + if (true) { + tmpImg.onload = function () { + // IE needs a pause... + setTimeout(function(){ + ctx.drawImage(tmpImg, 0, 0, w, h); + }, 200); + }; + + tmpImg.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); + // uriPayload = encodeURIComponent(svg.replace(/\n+/g, '')) // remove newlines // encode URL-unsafe characters + // .replace(/%20/g, ' ') // put spaces back in + // .replace(/%3D/g, '=') // ditto equals signs + // .replace(/%3A/g, ':') // ditto colons + // .replace(/%2F/g, '/') // ditto slashes + // .replace(/%22/g, "'"); + // tmpImg.src = 'data:image/svg+xml,' + uriPayload; + } else { + // Alternative version + var DOMURL = window.URL || window.webkitURL || window; + var svgBlob = new Blob([svg], {type: 'image/svg+xml'}); + var url = DOMURL.createObjectURL(svgBlob); + tmpImg.src = url; + + tmpImg.onload = function () { + // IE needs a pause... + setTimeout(function(){ + ctx.drawImage(tmpImg, 0, 0, w, h); + }, 200); + DOMURL.revokeObjectURL(url); + }; + } + + // Move all HTML tags back from + // the foreignObject element to the container + if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) { + while (this.foreignObjLayer.firstChild) { + this.container.appendChild(this.foreignObjLayer.firstChild); + } + } + + return this; + }, + + screenshot: function(board) { + var node, doc, cPos, + canvas, id, + img, + button, buttonText, + w, h, + bas = board.attr.screenshot, + zbar, zbarDisplay, + cssTxt; + + if (this.type === 'no') { + return; + } + + w = bas.scale * parseFloat(board.containerObj.style.width); + h = bas.scale * parseFloat(board.containerObj.style.height); + + // Create div which contains canvas element and close button + doc = board.containerObj.ownerDocument; + node = doc.createElement('div'); + node.style.cssText = bas.css; + node.style.width = (w) + 'px'; + node.style.height = (h) + 'px'; + node.style.zIndex = board.containerObj.style.zIndex + 120; + + // Position the div exactly over the JSXGraph board + cPos = board.getCoordsTopLeftCorner(); + node.style.position= 'absolute'; + node.style.left = (500+ cPos[0]) + 'px'; + node.style.top = (cPos[1]) + 'px'; + + // Create canvas element + 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'; + + img = new Image(); //doc.createElement('img'); + img.style.width = w + 'px'; + img.style.height = h + 'px'; + //img.crossOrigin = 'anonymous'; + + // 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(canvas); + node.appendChild(img); + node.appendChild(button); + board.containerObj.parentNode.appendChild(node); + + // Hide navigation bar in board + zbar = document.getElementById(board.containerObj.id + '_navigationbar'); + if (Type.exists(zbar)) { + zbarDisplay = zbar.style.display; + zbar.style.display = 'none'; + } + + // Create screenshot in canvas + this.dumpToCanvas(id, w, h); + // Show image in img tag + setTimeout(function() { + img.src = canvas.toDataURL('image/png'); + }, 400); + + // Show navigation bar in board + if (Type.exists(zbar)) { + zbar.style.display = zbarDisplay; + } + } + + }); + + return JXG.SVGRenderer; +}); + +/* + Copyright 2008-2017 + 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 + and . + */ + + +/*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}. + * @class JXG.AbstractRenderer + * @augments JXG.AbstractRenderer + * @param {Node} container Reference to a DOM node containing the board. + * @see JXG.AbstractRenderer + */ + 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(''); + }; + } 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 */ { + + /** + * Sets attribute key of node node to value. + * @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 + '
\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 = Type.evaluate(el.visProp.anchorx), + ev_ay = Type.evaluate(el.visProp.anchory); + + 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-2017 + 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 + and . + */ + + +/*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.AbstractRenderer + * @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) { + var i; + + 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>'].join(''); + this.canvasRoot = this.container.ownerDocument.getElementById(this.canvasId); + this.context = this.canvasRoot.getContext('2d'); + } else if (Env.isNode()) { + this.canvasId = (typeof module === 'object' ? module.require('canvas') : require('canvas')); + this.canvasRoot = new this.canvasId(500, 500); + this.context = this.canvasRoot.getContext('2d'); + } + + 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#makeArrows + * @private + */ + _drawFilledPolygon: function (shape) { + var i, len = shape.length, + context = this.context; + + if (len > 0) { + context.beginPath(); + context.moveTo(shape[0][0], shape[0][1]); + for (i = 0; i < len; i++) { + if (i > 0) { + context.lineTo(shape[i][0], shape[i][1]); + } + } + context.lineTo(shape[0][0], shape[0][1]); + context.fill(); + } + }, + + /** + * 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 (0, 0) 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 (0, 0). + * @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; + }, + + /** + * 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 fill or stroke. + * @param {String} [targetType=type] (optional) Either fill or stroke. + * @returns {Boolean} If the color could be set, true is returned. + * @private + */ + _setColor: function (el, type, targetType) { + var hasColor = true, isTrace = false, + ev = el.visProp, hl, sw, + rgba, rgbo, c, o, oo; + + type = type || 'stroke'; + targetType = targetType || type; + + 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 = ''; + } + + // 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.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 * + * ******************************** */ + + // documented in AbstractRenderer + drawLine: function (el) { + var obj, + 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, + ev_fa = Type.evaluate(el.visProp.firstarrow), + ev_la = Type.evaluate(el.visProp.lastarrow); + + if (!el.visPropCalc.visible) { + return; + } + + if (ev_fa || ev_la) { + margin = -4; + } + Geometry.calcStraight(el, c1, c2, margin); + + obj = this.getPositionArrowHead(el, c1, c2); + + this.context.beginPath(); + this.context.moveTo(obj.c1.scrCoords[1] + obj.d1x, obj.c1.scrCoords[2] + obj.d1y); + this.context.lineTo(obj.c2.scrCoords[1] - obj.d2x, obj.c2.scrCoords[2] - obj.d2y); + this._stroke(el); + + if ((ev_fa && obj.sFirst > 0) || + (ev_la && obj.sLast > 0)) { + this.makeArrows(el, obj.c1, obj.c2); + } + }, + + // 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, + 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]); + } + // 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) { + if (Type.evaluate(el.visProp.handdrawing)) { + this.updatePathStringBezierPrim(el); + } else { + this.updatePathStringPrim(el); + } + if (el.numberPoints > 1) { + this.makeArrows(el); + } + }, + + // 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), + ev_ax = Type.evaluate(el.visProp.anchorx), + ev_ay = Type.evaluate(el.visProp.anchory), + 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) + 'px 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 + makeArrows: function (el, scr1, scr2) { + // not done yet for curves and arcs. + var x1, y1, x2, y2, ang, + size, + w = Type.evaluate(el.visProp.strokewidth), + arrowHead, + arrowTail, + context = this.context, + type, + ev_fa = Type.evaluate(el.visProp.firstarrow), + ev_la = Type.evaluate(el.visProp.lastarrow); + + 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]; + } else { + x1 = el.points[0].scrCoords[1]; + y1 = el.points[0].scrCoords[2]; + x2 = el.points[el.points.length - 1].scrCoords[1]; + y2 = el.points[el.points.length - 1].scrCoords[2]; + return; + } + + if (ev_fa && + Type.exists(ev_fa.type)) { + + if (Type.exists(ev_fa.size)) { + size = Type.evaluate(ev_fa.size); + } else { + size = 3; + } + w *= size; + + type = Type.evaluate(ev_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 { + arrowTail = [ + [ w, -w * 0.5], + [ 0.0, 0.0], + [ w, w * 0.5] + ]; + } + + } + if (ev_la && + Type.exists(ev_la.type)) { + + if (Type.exists(ev_la.size)) { + size = Type.evaluate(ev_la.size); + } else { + size = 3; + } + w *= size; + + type = Type.evaluate(ev_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 { + arrowHead = [ + [ -w, -w * 0.5], + [ 0.0, 0.0], + [ -w, w * 0.5] + ]; + } + } + + context.save(); + if (this._setColor(el, 'stroke', 'fill')) { + ang = Math.atan2(y2 - y1, x2 - x1); + if (ev_la) { + this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowHead, ang), x2, y2)); + } + + if (ev_fa) { + this._drawFilledPolygon(this._translateShape(this._rotateShape(arrowTail, ang), x1, y1)); + } + } + context.restore(); + } + }, + + // 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; + } + + 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-2017 + 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 + and . + */ + + +/*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; + }()), + + 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; + } + + // 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; + }, + + /** + * 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 true, 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 {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 bet 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, + w, h, dimensions, + bbox, attr, axattr, axattr_x, axattr_y, + defaultaxesattr, + selectionattr, + board; + + attributes = attributes || {}; + + // merge attributes + attr = Type.copyAttributes(attributes, Options, 'board'); + attr.zoom = Type.copyAttributes(attr, Options, 'board', 'zoom'); + attr.pan = Type.copyAttributes(attr, Options, 'board', 'pan'); + attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); + attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); + + 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; + 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); + } else { + unitX = Math.abs(unitY) * unitX / Math.abs(unitX); + } + } else { + unitX = w / (bbox[2] - bbox[0]); + unitY = h / (bbox[1] - bbox[3]); + } + originX = -unitX * bbox[0]; + originY = unitY * bbox[1]; + } + + renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer); + + // 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); + + JXG.boards[board.id] = board; + + board.keepaspectratio = attr.keepaspectratio; + board.resizeContainer(dimensions.width, dimensions.height, true, true); + + // create elements like axes, grid, navigation, ... + board.suspendUpdate(); + board.initInfobox(); + + 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._createSelectionPolygon(attr); + /* + selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); + if (selectionattr.enabled === true) { + board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); + } + */ + + board.renderer.drawZoomBar(board, attr.navbar); + 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] + * @returns {JXG.Board} Reference to the created board. + * @see JXG.FileReader + * @see JXG.GeonextReader + * @see JXG.GeogebraReader + * @see JXG.IntergeoReader + * @see JXG.CinderellaReader + */ + loadBoardFromFile: function (box, file, format, attributes, callback) { + var attr, renderer, board, dimensions, + selectionattr; + + attributes = attributes || {}; + + // merge attributes + attr = Type.copyAttributes(attributes, Options, 'board'); + attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom'); + attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan'); + attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); + attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); + + dimensions = Env.getDimensions(box, attr.document); + renderer = this.initRenderer(box, dimensions, attr.document); + + /* 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); + board.initInfobox(); + board.resizeContainer(dimensions.width, dimensions.height, true, true); + + FileReader.parseFileContent(file, board, format, true, callback); + + selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); + board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); + + board.renderer.drawZoomBar(board, attr.navbar); + JXG.boards[board.id] = board; + + 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' or 'Intergeo'. + * @param {Object} [attributes] + * @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, dimensions, board, + selectionattr; + + attributes = attributes || {}; + + // merge attributes + attr = Type.copyAttributes(attributes, Options, 'board'); + attr.zoom = Type.copyAttributes(attributes, Options, 'board', 'zoom'); + attr.pan = Type.copyAttributes(attributes, Options, 'board', 'pan'); + attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection'); + attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar'); + + dimensions = Env.getDimensions(box, attr.document); + renderer = this.initRenderer(box, dimensions, attr.document); + + /* 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); + board.initInfobox(); + board.resizeContainer(dimensions.width, dimensions.height, true, true); + + FileReader.parseString(string, board, format, true, callback); + + selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection'); + board.selectionPolygon = board.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr); + + board.renderer.drawZoomBar(board, attr.navbar); + JXG.boards[board.id] = board; + + 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]; + } + + 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, width, height, bbox, axis, grid, code, + 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')) { + width = scripts[i].getAttribute('width', false) || '500px'; + height = scripts[i].getAttribute('height', false) || '500px'; + bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5'; + id = scripts[i].getAttribute('container', 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); + div.setAttribute('style', 'width:' + width + '; height:' + height + '; float:left'); + div.setAttribute('class', 'jxgbox'); + 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); + } + + if (document.getElementById(id)) { + code = scripts[i].innerHTML; + code = code.replace(//g, ''); + scripts[i].innerHTML = code; + + 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-2017 + 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 + and . + */ + + +/*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; + + 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); + }, + + /** + * 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 : []; + }, + + /** + * Sends an update to all group members. This method is called from the points' coords object event listeners + * and not by the board. + * @param{JXG.GeometryElement} drag Element that caused the update. + * @returns {JXG.Group} returns this group + */ + update: function (drag) { + var 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') { + 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)) { + obj = this.objects[el].point; + this.coords[obj.id] = {usrCoords: obj.coords.usrCoords.slice(0)}; + } + } + + 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.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 at 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); + * + *
+ *
+     *
+     *
+     * @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);
+     *
+     * 
+ *
+     *
+     *  @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]]);
+     *
+     * 
+ *
+     *
+     *  @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]]);
+     *
+     * 
+ *
+     *
+     *  @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]);
+     *
+     * 
+ *
+     *
+     *  @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);
+     *
+     * 
+ *
+     *
+     *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/point', 'base/curve'
+], function (JXG, Const, Coords, Mat, Numerics, Geometry, Type, Point, Curve) {
+
+    "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
+     * Optional parameters four and five are numbers which define the curve length (e.g. start/end). Default values are -pi and 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]);
+     * 
+ *
+     */
+    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
+     * Optional parameters four and five are numbers which define the curve length (e.g. start/end). Default values are -pi and 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]);
+     * 
+ *
+     */
+    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.
+     * 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]);
+     * 
+ *
+     */
+    JXG.createParabola = function (board, parents, attributes) {
+        var polarForm, curve, M, i,
+            // 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');
+
+        // focus 1 given by coordinates
+        if (parents[0].length > 1) {
+            F1 = board.create('point', parents[0], attr_foci);
+        // focus i 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 i 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]");
+        }
+
+        // to
+        if (!Type.exists(parents[3])) {
+            parents[3] = 10;
+        }
+
+        // from
+        if (!Type.exists(parents[2])) {
+            parents[2] = -10;
+        }
+
+        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.
+     * @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_12,a_22 6 numbers
+     * @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]);
+     * 
+ *
+     */
+    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*2.0, A5*0.5],
+             *   [A4*2.0,    -A0, A3*0.5],
+             *   [A5*0.5, A3*0.5,    -A1]]
+             * New: (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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/line', 'base/transformation'
+], function (JXG, Const, Coords, Statistics, Geometry, Type, GeometryElement, Line, Transform) {
+
+    "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,
+            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 polygon as child to defining points
+        for (i = 0; i < this.vertices.length - 1; i++) {
+            this.board.select(this.vertices[i]).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 */ {
+        /**
+         * 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, j, len, c = false;
+
+            if (Type.evaluate(this.visProp.hasinnerpoints)) {
+                // All points of the polygon trigger hasPoint: inner and boundary points
+                len = this.vertices.length;
+                // See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+                // for a reference of Jordan method
+                for (i = 0, j = len - 2; i < len - 1; j = i++) {
+                    if (((this.vertices[i].coords.scrCoords[2] > y) !== (this.vertices[j].coords.scrCoords[2] > y)) &&
+                            (x < (this.vertices[j].coords.scrCoords[1] - this.vertices[i].coords.scrCoords[1]) * (y - this.vertices[i].coords.scrCoords[2]) /
+                            (this.vertices[j].coords.scrCoords[2] - this.vertices[i].coords.scrCoords[2]) + this.vertices[i].coords.scrCoords[1])) {
+                        c = !c;
+                    }
+                }
+                if (c) {
+                    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)) {
+                    c = true;
+                    break;
+                }
+            }
+
+            return c;
+        },
+
+        /**
+         * Uses the boards renderer to update the polygon.
+         */
+        updateRenderer: function () {
+            var wasReal, i, len;
+
+            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);
+
+            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(); }]);
+         * 
+ *
+         *
+         */
+        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}.
+         */
+        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}
+         */
+        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 
+         * @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 wikipedia entry.
+        * 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 {@link JXG.Polygon#sutherlandHodgman}.
+         *
+         * @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'});
+         * 
+ *
+         *
+         * @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();
+         * 
+ *
+         *
+         */
+        intersect: function(polygon) {
+            return this.sutherlandHodgman(polygon);
+        }
+
+
+    });
+
+
+    /**
+     * @class A polygon is an area enclosed by a set of border lines which are determined by
+     * 
    + *
  • a list of points or + *
  • a list of coordinate arrays or + *
  • a function returning a list of coordinate arrays. + *
+ * 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. + * @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]); + *
+ *
+     *
+     * @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});
+     * 
+ *
+     *
+     * @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]);
+     *
+     * 
+ *
+     */
+    JXG.createPolygon = function (board, parents, attributes) {
+        var el, i, points = [],
+            attr, p;
+
+        points = Type.providePoints(board, parents, attributes, 'polygon', ['vertices']);
+        if (points === false) {
+            throw new Error("JSXGraph: Can't create polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates");
+        }
+
+        attr = Type.copyAttributes(attributes, board.options, 'polygon');
+        el = new JXG.Polygon(board, points, attr);
+        el.isDraggable = true;
+
+        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]);
+     * 
+ *
+     * @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]);
+     * 
+ *
+     */
+    JXG.createRegularPolygon = function (board, parents, attributes) {
+        var el, i, n,
+            p = [], rot, c, 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 polgonmes 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;
+    };
+
+    JXG.registerElement('polygon', JXG.createPolygon);
+    JXG.registerElement('regularpolygon', JXG.createRegularPolygon);
+
+    return {
+        Polygon: JXG.Polygon,
+        createPolygon: JXG.createPolygon,
+        createRegularPolygon: JXG.createRegularPolygon
+    };
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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',
+    'base/curve', 'element/composition'
+], function (JXG, Geometry, Mat, Coords, Circle, Type, Const, Curve, Compositions) {
+
+    "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]);
+     * 
+ *
+     */
+    JXG.createArc = function (board, parents, attributes) {
+        var el, attr, i, points;
+
+        // This method is used to create circumcirclearcs, too. If a circumcirclearc is created we get a fourth
+        // point, that's why we need to check that case, too.
+        points = Type.providePoints(board, parents, attributes, 'point');
+        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]");
+        }
+
+        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
+        el.center.addChild(el);
+        el.radiuspoint.addChild(el);
+        el.anglepoint.addChild(el);
+
+        // should be documented in options
+        el.useDirection = attr.usedirection;
+
+        // documented in JXG.Curve
+        el.updateDataArray = function () {
+            var ar, phi, v, 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, angle, alpha, beta,
+                invMat, c,
+                prec,
+                r = this.Radius(),
+                ev_s = Type.evaluate(this.visProp.selection);
+
+            prec = this.board.options.precision.hasPoint / 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.
+             */
+            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 ((ev_s === 'minor' && beta > Math.PI) ||
+                        (ev_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 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 angle, alpha, beta,
+                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),
+                ev_s = Type.evaluate(this.visProp.selection);
+
+            if (has) {
+                angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
+                alpha = 0;
+                beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
+
+                if ((ev_s === 'minor' && beta > Math.PI) ||
+                        (ev_s === 'major' && beta < Math.PI)) {
+                    alpha = beta;
+                    beta = 2 * Math.PI;
+                }
+                if (angle < alpha || angle > beta) {
+                    has = false;
+                }
+            }
+
+            return has;
+        };
+
+        // documented in geometry element
+        el.getTextAnchor = function () {
+            return this.center.coords;
+        };
+
+        // documented in geometry element
+        el.getLabelAnchor = function () {
+            var coords, 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);
+
+            // 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;
+
+            return new Coords(Const.COORDS_BY_USER, [pmc[1] + vecx, pmc[2] + vecy], 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 p1 and
+     * p2 and the midpoint of p1 and p2.
+     * @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]);
+     * 
+ *
+     */
+    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', 'midpoint');
+        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
+     * p1, p2, and p3 and the midpoint of the circumcircle of the three points. The arc is drawn
+     * counter-clockwise from p1 over p2 to p3.
+     * @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('arc', [p1, p2, p3]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     */
+
+    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]);
+     * 
+ *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/point', 'base/curve',
+    'base/transformation', 'element/composition'
+], function (JXG, Geometry, Mat, Statistics, Coords, Const, Type, Point, Curve, Transform, Compositions) {
+
+    "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,p1 A sector is defined by three points: The sector's center p1,
+     * a second point p2 defining the radius and a third point p3 defining the angle of the sector. The
+     * Sector is always drawn counter clockwise from p2 to p3
+     *
+     * 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]);
+     * 
+ *
+     *
+     * @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]);
+     *
+     * 
+ *
+     */
+    JXG.createSector = function (board, parents, attributes) {
+        var el, i, attr,
+            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 = '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';
+
+        if (type === '2lines') {
+            el.Radius = function () {
+                return Type.evaluate(parents[4]);
+            };
+
+            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: 'getRadius',
+                getRadius: 'getRadius',
+                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 */
+            el.point1.addChild(el);
+            el.point2.addChild(el);
+            el.point3.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: 'getRadius',
+                getRadius: 'getRadius',
+                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,
+                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);
+
+            prec = this.board.options.precision.hasPoint / 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, 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);
+
+            // 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;
+
+            return new Coords(Const.COORDS_BY_USER, [pmc[1] + vecx, pmc[2] + vecy], this.board);
+        };
+
+        /**
+         * Overwrite the Radius method of the sector.
+         * Used in {@link GeometryElement#setAttribute}.
+         * @param {Number, Function} value New radius.
+         */
+        el.setRadius = function (value) {
+            el.Radius = function () {
+                return Type.evaluate(value);
+            };
+        };
+
+        /**
+         * @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, len,
+                    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 p1 through
+     * p2 to p3.
+     * @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 p1 through p2 to p3.
+     * @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]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     */
+    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]);
+     * 
+ *
+     */
+    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 radius. 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 possiblity of input parameters are:
+     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from p1 to
+     * p3 around p2.
+     *
+     * 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); }]);
+     * 
+ *
+     *
+     * @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 });
+     *
+     *
+     * 
+ *
+     */
+    JXG.createAngle = function (board, parents, attributes) {
+        var el, radius, text, attr, attrsub,
+            i, dot, 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 Angle.prototype#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];
+
+            el.Radius = function () {
+                return Type.evaluate(radius);
+            };
+
+            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.
+            * @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
+            *
+            * @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();
+            *
+            * 
+ *
+            *
+            * @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();
+            *
+            * 
+ *
+            *
+            */
+            el.setAngle = function (val) {
+                var t,
+                    p = this.anglepoint,
+                    q = this.radiuspoint;
+
+                if (p.draggable()) {
+                    t = this.board.create('transform', [val, this.center], {type: 'rotate'});
+                    p.addTransform(q, t);
+                    p.isDraggable = false;
+                    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
+            */
+            el.free = function () {
+                var p = this.anglepoint;
+                if (p.transformations.length > 0) {
+                    p.transformations.pop();
+                    p.isDraggable = true;
+                    p.parents = [];
+                }
+                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 dot.visible: true 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,
+                point1, point2, point3,
+                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, dy = 12,
+                A, B, r, d, a2, co, si, mat,
+                vp_s = Type.evaluate(el.visProp.selection);
+
+            // 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 = dy = Type.evaluate(this.label.visProp.fontSize);
+            }
+            dx /= this.board.unitX;
+            dy /= this.board.unitY;
+
+            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];
+
+            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); }]);
+     * 
+ *
+     */
+    JXG.createNonreflexAngle = function (board, parents, attributes) {
+        var el;
+
+        attributes.selection = 'minor';
+        el = JXG.createAngle(board, parents, attributes);
+
+        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); }]);
+     * 
+ *
+     */
+    JXG.createReflexAngle = function (board, parents, attributes) {
+        var el;
+
+        attributes.selection = 'major';
+        el = JXG.createAngle(board, parents, attributes);
+
+        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-2014
+ 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 
+ and .
+ */
+
+
+/*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', 'base/constants', 'base/curve'
+], function (JXG, Symbolic, Type, Const, Curve) {
+
+    "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'});
+     * 
+ *
+     */
+    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-2017
+        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 
+    and .
+ */
+
+
+/*global JXG: true, define: true*/
+/*jslint nomen: true, plusplus: true*/
+
+/* depends:
+ jxg
+ base/constants
+ base/coords
+ base/element
+ math/math
+ math/statistics
+ utils/type
+ */
+
+/**
+ * @fileoverview In this file the geometry element Image is defined.
+ */
+
+define('base/image',[
+    'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'math/statistics', 'utils/type', 'base/coordselement'
+], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Type, CoordsElement) {
+
+    "use strict";
+
+    /**
+     * Construct and handle images
+     * The coordinates can be relative to the coordinates of an element
+     * given in {@link JXG.Options#text.anchor}.
+     *
+     * 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 text is drawn on.
+     * @param {Array} coordinates An array with the user coordinates of the text.
+     * @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,
+                c, v, p, dot,
+                len = this.transformations.length;
+
+            // Easy case: no transformation
+            if (len === 0) {
+                dx = x - this.coords.scrCoords[1];
+                dy = this.coords.scrCoords[2] - y;
+                r = this.board.options.precision.hasPoint;
+
+                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', ['http://jsxgraph.uni-bayreuth.de/distrib/images/uccellino.jpg',
+         *                                [-3,-2], [3,3]]);
+         * im.setSize(4, 4);
+         * board.update();
+         *
+         * 
+ *
+         *
+         * @example
+         * var p0 = board.create('point', [-3, -2]),
+         *     im = board.create('image', ['http://jsxgraph.uni-bayreuth.de/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();
+         *
+         * 
+ *
+         *
+         */
+        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 GEONExT
+     *   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.
+     * 

+ * The array size defines the image's width and height in user coordinates. + * @example + * var im = board.create('image', ['http://jsxgraph.uni-bayreuth.de/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]); + * + *

+ *
+     */
+    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 (Type.evaluate(attr.rotate) !== 0) {
+            im.addRotation(Type.evaluate(attr.rotate));
+        }
+
+        return im;
+    };
+
+    JXG.registerElement('image', JXG.createImage);
+
+    return {
+        Image: JXG.Image,
+        createImage: JXG.createImage
+    };
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text'
+], function (JXG, Mat, Geometry, 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;
+
+        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 null.
+         * @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.
+         * @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 = this.board.options.precision.hasPoint +
+                        Type.evaluate(this.visProp.strokewidth) * 0.5;
+
+            if (!Type.evaluate(this.line.visProp.scalable)) {
+                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 (!Type.evaluate(this.line.visProp.scalable)) {
+                return this;
+            }
+
+            // horizontal line
+            if (Math.abs(this.line.stdform[1]) < Mat.eps && Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) {
+                dx = oldc.usrCoords[1] / c.usrCoords[1];
+                bb[0] *= dx;
+                bb[2] *= dx;
+                this.board.setBoundingBox(bb, false);
+            // vertical line
+            } else if (Math.abs(this.line.stdform[2]) < Mat.eps && Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) {
+                dy = oldc.usrCoords[2] / c.usrCoords[2];
+                bb[3] *= dy;
+                bb[1] *= dy;
+                this.board.setBoundingBox(bb, false);
+            }
+
+            return this;
+        },
+
+        /**
+         * (Re-)calculates the ticks coordinates.
+         * @private
+         */
+        calculateTicksCoordinates: function () {
+            var coordsZero, bounds;
+
+            // 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
+            coordsZero = this.getZeroCoordinates();
+
+            // Calculate lower bound and upper bound limits based on distance between p1 and centre and p2 and centre
+            bounds = this.getLowerAndUpperBounds(coordsZero);
+
+            // 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 () {
+            var d,
+                distMaj = Type.evaluate(this.visProp.majorheight) * 0.5,
+                distMin = Type.evaluate(this.visProp.minorheight) * 0.5;
+
+            // 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,
+                ev_a = Type.evaluate(this.visProp.anchor);
+
+            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;
+            } else 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);
+            } else 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;
+        },
+
+        /**
+         * 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 i s 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,
+                // The line's defining points that will be adjusted to be within the board limits
+                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),
+                // 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),
+                obj;
+
+            // 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
+            obj = this.board.renderer.getPositionArrowHead(this.line, point1, point2,
+                        Type.evaluate(this.line.visProp.strokewidth));
+            point1.setCoordinates(Const.COORDS_BY_SCREEN, [
+                    point1.scrCoords[1] - obj.d1x,
+                    point1.scrCoords[2] - obj.d1y,
+                ]);
+            point2.setCoordinates(Const.COORDS_BY_SCREEN, [
+                    point2.scrCoords[1] - obj.d2x,
+                    point2.scrCoords[2] - obj.d2y,
+                ]);
+
+            // Calculate 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
+         *
+         * @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 eps = Mat.eps,
+                distance = zero.distance(Const.COORDS_BY_USER, point);
+
+            // Establish sign
+            if (this.line.type === Const.OBJECT_TYPE_AXIS) {
+                if ((Mat.relDif(zero.usrCoords[1], point.usrCoords[1]) > eps &&
+                        zero.usrCoords[1] - point.usrCoords[1] > eps) ||
+                    (Mat.relDif(zero.usrCoords[2], point.usrCoords[2]) > eps &&
+                        zero.usrCoords[2] - point.usrCoords[2] > eps)) {
+
+                    distance *= -1;
+                }
+            } else if (Type.evaluate(this.visProp.anchor) === 'right') {
+                if (Geometry.isSameDirection(zero, this.line.point1.coords, point)) {
+                    distance *= -1;
+                }
+            } else {
+                if (!Geometry.isSameDirection(zero, this.line.point2.coords, point)) {
+                    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,
+                // Calculate X and Y distance between two major ticks
+                deltas = this.getXandYdeltas(),
+                // 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);
+
+            // 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) {
+                // Only draw ticks when we are within bounds, ignore case where  tickPosition < lower < upper
+                if (tickPosition >= bounds.lower) {
+                    this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
+                }
+                tickPosition += ticksDelta;
+            }
+
+            // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary
+            tickPosition = -ticksDelta;
+            while (tickPosition >= bounds.lower) {
+                // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition
+                if (tickPosition <= bounds.upper) {
+                    this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
+                }
+                tickPosition -= ticksDelta;
+            }
+        },
+
+        /**
+         * 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;
+
+            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));
+            while (distScr / (Type.evaluate(this.visProp.minorticks) + 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;
+
+            // Calculates tick coordinates
+            x = coordsZero.usrCoords[1] + tickPosition * deltas.x;
+            y = coordsZero.usrCoords[2] + tickPosition * deltas.y;
+            tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
+
+            // 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.tickEndings(tickCoords, tickCoords.major);
+            if (ti.length === 3) {
+                this.ticks.push(ti);
+                if (tickCoords.major && Type.evaluate(this.visProp.drawlabels)) {
+                    this.labelsData.push(
+                        this.generateLabelData(
+                            this.generateLabelText(tickCoords, coordsZero),
+                            tickCoords,
+                            this.ticks.length
+                        )
+                    );
+                } else {
+                    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,
+                hasLabelOverrides = Type.isArray(this.visProp.labels),
+                // Calculate X and Y distance between two major points in the line
+                deltas = this.getXandYdeltas(),
+                ev_dl = Type.evaluate(this.visProp.drawlabels);
+
+            for (i = 0; i < this.fixedTicks.length; i++) {
+                x = coordsZero.usrCoords[1] + this.fixedTicks[i] * deltas.x;
+                y = coordsZero.usrCoords[2] + this.fixedTicks[i] * deltas.y;
+                tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
+
+                // Compute the start position and the end position of a tick.
+                // If tick is out of the canvas, ti is empty.
+                ti = this.tickEndings(tickCoords, true);
+                if (ti.length === 3 && this.fixedTicks[i] >= bounds.lower &&
+                    this.fixedTicks[i] <= bounds.upper) {
+                    this.ticks.push(ti);
+
+                    if (ev_dl &&
+                            (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) {
+                        labelText = hasLabelOverrides ?
+                                        Type.evaluate(this.visProp.labels[i]) : this.fixedTicks[i];
+                        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 {
+                // 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 start and end 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
+         */
+        tickEndings: function (coords, major) {
+            var c, lineStdForm, intersection,
+                dxs, dys,
+                style,
+                x = [-2000000, -2000000],
+                y = [-2000000, -2000000];
+
+            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 (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 {
+                x[0] = c[1] + dxs * Type.evaluate(this.visProp.tickendings[0]);
+                y[0] = c[2] - dys * Type.evaluate(this.visProp.tickendings[0]);
+                x[1] = c[1] - dxs * Type.evaluate(this.visProp.tickendings[1]);
+                y[1] = c[2] + dys * 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 = value.toString(),
+                ev_s = Type.evaluate(this.visProp.scalesymbol);
+
+            // if value is Number
+            if (Type.isNumber(value)) {
+                if (labelText.length > Type.evaluate(this.visProp.maxlabellength) ||
+                        labelText.indexOf('e') !== -1) {
+                    labelText = value.toPrecision(Type.evaluate(this.visProp.precision)).toString();
+                }
+                if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) {
+                    // trim trailing zeros
+                    labelText = labelText.replace(/0+$/, '');
+                    // trim trailing .
+                    labelText = labelText.replace(/\.$/, '');
+                }
+            }
+
+            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;
+        },
+
+        /**
+         * 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 = this.getDistanceFromZero(zero, tick);
+
+            if (Math.abs(distance) < Mat.eps) { // Point is zero
+                labelText = '0';
+            } else {
+                // No value provided, equidistant, so assign distance as value
+                if (!Type.exists(value)) { // could be null or undefined
+                    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 *= 1.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
+            lenLabels = this.labels.length;
+
+            for (i = 0, j = 0; i < lenData; i++) {
+                if (this.labelsData[i] === null) {
+                    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);
+                }
+
+                visible = Type.evaluate(this.visProp.label.visible);
+                if (visible === 'inherit') {
+                    visible = this.visPropCalc.visible;
+                }
+                label.prepareUpdate()
+                    .updateVisibility(visible)
+                    .updateRenderer();
+                //this.board.renderer.display(label, visible);
+
+                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 = 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.
+     * @pseudo
+     * @description
+     * @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} line The parents consist of the line the ticks are going to be attached to.
+     * @param {Number} distance Number defining the distance between two major ticks or an
+     * array defining static ticks. 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 "middle".
+     *
+     * @example
+     * // Create an axis providing two coord 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});
+     * 
+ *
+     */
+    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) {
+            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 Hashes can be used to mark congruent lines.
+     * @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} line The line the hatch marks are going to be attached to.
+     * @param {Number} numberofhashes Number of dashes.
+     * @example
+     * // Create an axis providing two coord 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]);
+     * 
+ *
+     *
+     * @example
+     * // Alter the position of the hatch
+     * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-10, 10, 10, -5], keepaspectratio:true});
+     *
+     * 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});
+     *
+     * 
+ *
+     *
+     */
+    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 || 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
+    };
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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', 'utils/type', 'base/point', 'base/group',
+    'base/element', 'base/line', 'base/ticks', 'base/text'
+], function (JXG, Mat, Const, Type, Point, Group, GeometryElement, Line, Ticks, Text) {
+
+    "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]]);
+     * 
+ *
+     * @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 }});
+     * 
+ *
+     */
+    JXG.createSlider = function (board, parents, attributes) {
+        var pos0, pos1, smin, start, smax, sdiff,
+            p1, p2, l1, ticks, ti, startx, starty, p3, l2, t,
+            g,
+            withText, withTicks, snapWidth, attr, precision, el;
+
+        attr = Type.copyAttributes(attributes, board.options, 'slider');
+        withTicks = attr.withticks;
+        withText = attr.withlabel;
+        snapWidth = attr.snapwidth;
+        precision = attr.precision;
+
+        // 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]);
+
+        // slide 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;
+
+        startx = pos0[0] + (pos1[0] - pos0[0]) * (start - smin) / (smax - smin);
+        starty = pos0[1] + (pos1[1] - pos0[1]) * (start - 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
+        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
+         * @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,
+                        sl = Type.evaluate(p3.visProp.suffixlabel),
+                        ul = Type.evaluate(p3.visProp.unitlabel),
+                        pl = Type.evaluate(p3.visProp.postlabel);
+
+                    if (sl !== null) {
+                        n = sl;
+                    } else if (p3.name && p3.name !== '') {
+                        n = p3.name + ' = ';
+                    } else {
+                        n = '';
+                    }
+
+                    n += Type.toFixed(p3.Value(), precision);
+
+                    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 = p1p3.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);
+        }
+
+        // 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();
+// //                }
+//             }
+//         };
+
+        return p3;
+    };
+
+    JXG.registerElement('slider', JXG.createSlider);
+
+    return {
+        createSlider: JXG.createSlider
+    };
+});
+
+/*
+    Copyright 2008-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/point', 'base/line', 'base/ticks'
+], function (JXG, Type, GeometryElement, Point, Line, Ticks) {
+
+    "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 atape 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'});
+     * 
+ *
+     */
+    JXG.createTapemeasure = function (board, parents, attributes) {
+        var pos0, pos1,
+            attr, withTicks, withText, precision,
+            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;
+        precision = 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), precision);
+            });
+        }
+
+        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);
+        };
+
+        /** @ignore */
+        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();
+        }
+
+        return li;
+    };
+
+    JXG.registerElement('tapemeasure', JXG.createTapemeasure);
+
+    return {
+        createTapemeasure: JXG.createTapemeasure
+    };
+});
+
+/*
+    Copyright 2008-2013
+        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 
+    and .
+ */
+
+
+/*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-2017
+        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 
+    and .
+ */
+
+
+/*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";
+
+    /**
+     * Chart plotting
+     */
+    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 */ {
+        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);
+        },
+
+        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);
+        },
+
+        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);
+        },
+
+        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;
+        },
+
+        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;
+        },
+
+        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};
+        },
+
+        /*
+         * labelArray=[ row1, row2, row3 ]
+         * paramArray=[ paramx, paramy, paramz ]
+         * parents=[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
+         */
+        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) {
+                JXG.debug("No data");
+                return;
+            }
+            // labels for axes
+            paramArray = attributes.paramarray;
+            if (!Type.exists(paramArray)) {
+                JXG.debug("Need paramArray attribute");
+                return;
+            }
+            numofparams = paramArray.length;
+            if (numofparams <= 1) {
+                JXG.debug("Need more than 1 param");
+                return;
+            }
+
+            for (i = 0; i < len; i++) {
+                if (numofparams !== parents[i].length) {
+                    JXG.debug("Use data length equal to number of params (" + parents[i].length + " != " + numofparams + ")");
+                    return;
+                }
+            }
+
+            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) {
+                JXG.debug("Start shifts length is not equal to number of parameters");
+                return;
+            }
+
+            if (eshifts.length !== numofparams) {
+                JXG.debug("End shifts length is not equal to number of parameters");
+                return;
+            }
+
+            if (starts.length !== numofparams) {
+                JXG.debug("Starts length is not equal to number of parameters");
+                return;
+            }
+
+            if (ends.length !== numofparams) {
+                JXG.debug("Ends length is not equal to number of parameters");
+                return;
+            }
+
+            // 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) {
+                    JXG.debug("Too less circles");
+                    return;
+                }
+
+                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
+            };
+        },
+
+        /**
+         * Then, the update function of the renderer
+         * is called.  Since a chart is only an abstract element,
+         * containing other elements, this function is empty.
+         */
+        updateRenderer: function () {
+            return this;
+        },
+
+        /**
+         * Update of the defining points
+         */
+        update: function () {
+            if (this.needsUpdate) {
+                this.updateDataArray();
+            }
+
+            return this;
+        },
+
+        /**
+         * For dynamic charts update
+         * can be used to compute new entries
+         * for the arrays this.dataX and
+         * this.dataY. It is used in @see update.
+         * Default is an empty method, can be overwritten
+         * by the user.
+         */
+        updateDataArray: function () {}
+    });
+
+    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
+     *
+     **/
+    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();
+
+    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.strokecolor = this.color_array[i];
+            this.myAtts.highlightstrokecolor = this.color_array[i];
+            this.myAtts.name = this.label_array[i];
+            this.myAtts.label = {
+                offset: [10, 0],
+                strokeColor: this.color_array[i],
+                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;
+
+        }
+    };
+
+    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)) {
+            if (parents.length === 2) {
+                start_from = parents;
+            }
+        }
+
+        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-2017
+        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 
+    and .
+ */
+
+
+/*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', 'base/curve', 'base/point', 'base/line', 'base/transformation'
+], function (JXG, Const, GeometryElement, Type, Curve, Point, Line, Transform) {
+
+    "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.
+     */
+    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
+    };
+});
+
+/*
+ JessieCode Computer algebra algorithms
+
+    Copyright 2011-2016
+        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 
+    and .
+ */
+
+ /*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 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 code 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 '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 '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 'atan2':
+             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);
+                 this._error('Derivative of "' + fun + '" not yet implemented');
+             }
+
+             return newNode;
+         },
+
+         derivative: function(node, varname) {
+             var i, len, 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 {
+                         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],
+                 newNode;
+
+             // 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-2013
+        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 
+    and .
+ */
+
+
+/*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 s 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 instance 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]);
+            }
+
+            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]) {
+                        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(), true]
+            });
+
+            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);
+
+            // This has been moved to toJavaScript and toJessie
+            /*
+            methods.push({
+                obj: '$board',
+                method: 'setBoundingBox',
+                params: [board.getBoundingBox(), true]
+            });
+            */
+
+            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 a. 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 s, 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, '\\$1') + '\'';
+            case 'number':
+            case 'boolean':
+                return obj.toString();
+            case 'null':
+                return 'null';
+            }
+        },
+
+        /**
+         * Saves the construction in board to JessieCode.
+         * @param {JXG.Board} board
+         * @returns {String} JessieCode
+         */
+        toJessie: function (board) {
+            var i, elements,
+                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') + ';');
+                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 board to JavaScript.
+         * @param {JXG.Board} board
+         * @returns {String} JavaScript
+         */
+        toJavaScript: function (board) {
+            var i, elements,
+                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) + ');');
+            }
+
+            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 2008-2014
+        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 
+    and .
+ */
+
+
+/*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', 'options', 'utils/type', 'base/constants', 'base/line', 'base/polygon', 'base/point', 'base/element'
+], function (JXG, Options, Type, Const, Line, Polygon, Point, GeometryElement) {
+
+    "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]);
+     *
+     * 
+ *
+     *
+     * @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]);
+     *
+     * 
+ *
+     */
+    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);
+
+        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-2017
+        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 
+    and .
+ */
+
+
+/*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.
+     *
+     * @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.
+     *                     

+ * 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. + *

+ * The label of the input element may be given as string. + * + * @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; + * }]); + *

+ *
+     *
+     * 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);
+     * 
+ *
+     */
+    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],
+            '
' + + '' + + '
' + ]; + + //t = JXG.createText(board, par, attr); + t = board.create('text', par, attr); + t.type = Type.OBJECT_TYPE_CHECKBOX; + + t.rendNodeForm = t.rendNode.childNodes[0]; + t.rendNodeForm.id = t.rendNode.id + '_form'; + + t.rendNodeCheckbox = t.rendNodeForm.childNodes[0]; + t.rendNodeCheckbox.id = t.rendNode.id + '_checkbox'; + + t.rendNodeTag = t.rendNodeCheckbox; // Needed for unified treatment in setAttribute + t.rendNodeTag.disabled = !!attr.disabled; + + t.rendNodeLabel = t.rendNodeForm.childNodes[1]; + t.rendNodeLabel.id = t.rendNode.id + '_label'; + t.rendNodeLabel.innerHTML = parents[2]; + + // This sets the font-size of the checkbox itself + t.visPropOld.fontsize = "0px"; + board.renderer.updateTextStyle(t, false); + + t._value = false; + + t.Value = function () { + return this._value; + }; + + t.update = function () { + if (this.needsUpdate) { + 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-2017 + 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 + and . + */ + + +/*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 form input element. + *

+ * If the width of element is set with the attribute "cssStyle", the width of the label must be added. + * @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. + *

+ * 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. + *

+ * The default value of the input element may be given as string. + *

+ * 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() {text:dsddfghj + * 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, '']); + * + * var updateGraph = function() { + * graph.Y = board.jc.snippet(input.Value(), true, 'x', false); + * graph.updateCurve(); + * board.update(); + * } + *

+ *
+     */
+    JXG.createInput = function (board, parents, attributes) {
+        var t, par,
+            attr = Type.copyAttributes(attributes, board.options, 'input');
+
+        //if (parents.length !== 4) {
+            //throw new Error("JSXGraph: Can't create input with parent types '" +
+            //    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
+            //    "\nPossible parents are: [x, y, value, label]");
+        //}
+
+        par = [parents[0], parents[1],
+            '
' + + '' + + '
' + ]; + + //t = JXG.createText(board, par, attr); + t = board.create('text', par, attr); + t.type = Type.OBJECT_TYPE_INPUT; + + t.rendNodeForm = t.rendNode.childNodes[0]; + t.rendNodeForm.id = t.rendNode.id + '_form'; + + t.rendNodeLabel = t.rendNodeForm.childNodes[0]; + t.rendNodeLabel.id = t.rendNode.id + '_label'; + t.rendNodeLabel.innerHTML = parents[3]; + + t.rendNodeInput = t.rendNodeForm.childNodes[1]; + t.rendNodeInput.id = t.rendNode.id + '_input'; + t.rendNodeInput.value = parents[2]; + + t.rendNodeTag = t.rendNodeInput; // Needed for unified treatment in setAttribute + t.rendNodeTag.disabled = !!attr.disabled; + + // This sets the font-size of the input HTML element + t.visPropOld.fontsize = "0px"; + board.renderer.updateTextStyle(t, false); + + t._value = parents[2]; + + t.Value = function () { + return this._value; + }; + + t.update = function () { + if (this.needsUpdate) { + this._value = this.rendNodeInput.value; + } + return this; + }; + + Env.addEvent(t.rendNodeInput, 'input', priv.InputInputEventHandler, t); + + Env.addEvent(t.rendNodeInput, 'mousedown', function(evt) { evt.stopPropagation(); }, t); + Env.addEvent(t.rendNodeInput, 'touchstart', function(evt) { evt.stopPropagation(); }, t); + Env.addEvent(t.rendNodeInput, 'pointerdown', function(evt) { evt.stopPropagation(); }, t); + + return t; + }; + + JXG.registerElement('input', JXG.createInput); + + return { + createInput: JXG.createInput + }; +}); + +/* + Copyright 2008-2017 + 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 + and . + */ + + +/*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. + * + * @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. + *

+ * 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. + *

+ * The label of the input element may be given as string. + *

+ * 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;" + * ], {}); + * + *

+ *
+     * [x, y, label, handler]
+     */
+    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], ''];
+
+        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) { evt.stopPropagation(); }, t);
+        Env.addEvent(t.rendNodeButton, 'touchstart', function(evt) { evt.stopPropagation(); }, t);
+        Env.addEvent(t.rendNodeButton, 'pointerdown', function(evt) { evt.stopPropagation(); }, t);
+
+        return t;
+    };
+
+    JXG.registerElement('button', JXG.createButton);
+
+    return {
+        createButton: JXG.createButton
+    };
+});
+
+/*global define: true*/
+define('../build/core.deps.js',[
+    'jxg',
+    'utils/env',
+    'base/constants',
+    'utils/type',
+    'utils/xml',
+    'utils/event',
+    'utils/expect',
+    'math/math',
+    'math/qdt',
+    'math/numerics',
+    'math/statistics',
+    'math/symbolic',
+    'math/geometry',
+    'math/poly',
+    'math/complex',
+    'renderer/abstract',
+    'renderer/no',
+    'reader/file',
+    'parser/geonext',
+    'base/board',
+    'options',
+    'jsxgraph',
+    'base/element',
+    'base/coords',
+    '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/slopetriangle',
+    'element/checkbox',
+    'element/input',
+    'element/button'
+], function (JXG, Env) {
+    "use strict";
+
+    // we're in the browser, export JXG to the global JXG symbol for backwards compatiblity
+    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');
+}));
\ No newline at end of file
diff --git a/src/themes/gui.js b/src/themes/gui.js
index 6c6d0cf75..03341b815 100644
--- a/src/themes/gui.js
+++ b/src/themes/gui.js
@@ -128,7 +128,7 @@ JXG.Options = JXG.merge(JXG.Options, {
         fillColor: '#ffff00',
         highlightFillColor: '#ffff00',
         hasInnerPoints: true,
-
+        
         borders: {
             strokeColor: '#444444',
             strokeOpacity: 0.9,