/*
    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 <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


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

/* depends:
 jxg
 math/math
 math/geometry
 math/numerics
 math/statistics
 base/constants
 base/coords
 base/element
 utils/type
  elements:
   transform
   point
   ticks
 */

/**
 * @fileoverview The geometry object Line is defined in this file. Line stores all
 * style and functional properties that are required to draw and move a line on
 * a board.
 */

define([
    'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords',
    'base/element', 'utils/type', 'base/transformation', 'base/point', 'base/ticks'
], function (JXG, Mat, Geometry, Numerics, Statistics, Const, Coords, GeometryElement, Type, Transform, Point, Ticks) {

    "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 #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.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.
         * @return {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;

            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 > this.board.options.precision.hasPoint) {
                return false;
            }

            if (this.visProp.straightfirst && 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 (!this.visProp.straightfirst && pos < 0) {
                return false;
            }

            if (!this.visProp.straightlast && pos > 1) {
                return false;
            }
            return true;
        },

        // document in base/element
        update: function () {
            var funps;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.constrained) {
                if (typeof this.funps === 'function') {
                    funps = this.funps();
                    if (funps && funps.length && funps.length === 2) {
                        this.point1 = funps[0];
                        this.point2 = funps[1];
                    }
                } else {
                    if (typeof this.funp1 === 'function') {
                        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 (typeof this.funp2 === 'function') {
                        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 (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) && !this.point1.visProp.fixed;
                drag2 = this.point2.isDraggable && (this.point2.type !== Const.OBJECT_TYPE_GLIDER) && !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.prepareUpdate().updateRenderer();
                    } 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.prepareUpdate().updateRenderer();
                    }
                    // 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.prepareUpdate().updateRenderer();
                    } else if (drag1) {
                        this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
                            this.point2.X() + x * dnew / d,
                            this.point2.Y() + y * dnew / d
                        ]);
                        this.point1.prepareUpdate().updateRenderer();
                    }
                }
                // 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 && this.visProp.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 (this.isReal) {
                    if (wasReal !== this.isReal) {
                        this.board.renderer.show(this);
                        if (this.hasLabel && this.label.visProp.visible) {
                            this.board.renderer.show(this.label);
                        }
                    }
                    this.board.renderer.updateLine(this);
                } else {
                    if (wasReal !== this.isReal) {
                        this.board.renderer.hide(this);
                        if (this.hasLabel && this.label.visProp.visible) {
                            this.board.renderer.hide(this.label);
                        }
                    }
                }

                this.needsUpdate = false;
            }

            /* Update the label if visible. */
            if (this.hasLabel && this.label.visProp.visible && this.isReal) {
                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            return this;
        },

        /**
         * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1}
         * and {@link #point2}.
         * @param {JXG.Point} p The point for that the polynomial is generated.
         * @return {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 #point1} and {@link #point2} and updates the line.
         * @param {Boolean} straightFirst True if the Line shall be drawn beyond {@link #point1}, false otherwise.
         * @param {Boolean} straightLast True if the Line shall be drawn beyond {@link #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,
                sx = 0,
                sy = 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);

            if (this.visProp.straightfirst || this.visProp.straightlast) {
                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 (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 label offsets if the label seems to be outside of camvas.
            if (this.visProp.straightfirst || this.visProp.straightlast) {
                if (Type.exists(this.label)) {  // Does not exist during createLabel
                    sx = parseFloat(this.label.visProp.offset[0]);
                    sy = parseFloat(this.label.visProp.offset[1]);
                    fs = this.label.visProp.fontsize;
                }

                if (Math.abs(x) < Mat.eps) {
                    x = sx;
                } else if (this.board.canvasWidth + Mat.eps > x && x > this.board.canvasWidth - fs - Mat.eps) {
                    x = this.board.canvasWidth - sx - fs;
                }

                if (Mat.eps + fs > y && y > -Mat.eps) {
                    y = sy + fs;
                } else if (this.board.canvasHeight + Mat.eps > y && y > this.board.canvasHeight - fs - Mat.eps) {
                    y = this.board.canvasHeight - sy;
                }
            }

            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;
        },

        /**
         * Apply a translation by <tt>tv = (x, y)</tt> to the 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} tv (x, y)
         * @returns {JXG.Line} Reference to this line object.
         */
        setPosition: 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.point1.transformations.length > 0 && this.point1.transformations[this.point1.transformations.length - 1].isNumericMatrix) {
                this.point1.transformations[this.point1.transformations.length - 1].melt(t);
            } else {
                this.point1.addTransform(this.point1, t);
            }
            if (this.point2.transformations.length > 0 && this.point2.transformations[this.point2.transformations.length - 1].isNumericMatrix) {
                this.point2.transformations[this.point2.transformations.length - 1].melt(t);
            } else {
                this.point2.addTransform(this.point2, t);
            }

            return this;
        },

        /**
         * Moves the line 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.Line} this element
         */
        setPositionDirectly: function (method, coords, oldcoords) {
            var dc, t,
                c = new Coords(method, coords, this.board),
                oldc = new Coords(method, oldcoords, this.board);

            if (!this.point1.draggable() || !this.point2.draggable()) {
                return this;
            }

            dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
            t = this.board.create('transform', dc.slice(1), {type: 'translate'});
            t.applyOnce([this.point1, this.point2]);

            return this;
        },

        // see GeometryElement.js
        snapToGrid: function (pos) {
            var c1, c2, dc, t, v, ticks,
                x, y, sX, sY;

            if (this.visProp.snaptogrid) {
                if (this.parents.length < 3) {    // Line through two points
                    this.point1.handleSnapToGrid(true);
                    this.point2.handleSnapToGrid(true);
                /*
                if (this.point1.visProp.snaptogrid || this.point2.visProp.snaptogrid) {
                    this.point1.snapToGrid();
                    this.point2.snapToGrid();
                */
                } else if (JXG.exists(pos)) {       // Free line
                    sX = this.visProp.snapsizex;
                    sY = 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 * (ticks.visProp.minorticks + 1);
                    }
                    if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
                        ticks = this.board.defaultAxes.y.defaultTicks;
                        sY = ticks.ticksDelta * (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);

                        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.snapToGrid();
                this.point2.snapToGrid();
            }

            return this;
        },

        // see element.js
        snapToPoints: function () {
            var forceIt = 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 #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 #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 i, 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 <tt>a*x+b*y+c*z = 0</tt>. 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]]);
     * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
     *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
     * </script><pre>
     * @example
     * // Create a line using three coordinates
     * var l1 = board.create('line', [1.0, -2.0, 3.0]);
     * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
     * </script><pre>
     */
    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]) || parents[0].elementClass === Const.OBJECT_CLASS_POINT) {
                p1 =  board.select(parents[0]);
            } else if ((typeof parents[0] === 'function') && (parents[0]().elementClass === Const.OBJECT_CLASS_POINT)) {
                p1 = parents[0]();
                constrained = true;
            } else if ((typeof parents[0] === 'function') && (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]) || parents[1].elementClass === Const.OBJECT_CLASS_POINT) {
                p2 =  board.select(parents[1]);
            } else if ((typeof parents[1] === 'function') && (parents[1]().elementClass === Const.OBJECT_CLASS_POINT)) {
                p2 = parents[1]();
                constrained = true;
            } else if ((typeof parents[1] === 'function') && (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.parents = [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 (typeof parents[i] === 'number') {
                    // 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 (typeof parents[i] === 'function') {
                    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;

            if (isDraggable) {
                el.parents = [c[0](), c[1](), c[2]()];
            }
        // The parent array contains a function which returns two points.
        } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 2) &&
                (parents[0]()[0].elementClass === Const.OBJECT_CLASS_POINT) &&
                (parents[0]()[1].elementClass === Const.OBJECT_CLASS_POINT)) {
            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];
        } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 3) &&
                (typeof parents[0]()[0] === 'number') &&
                (typeof parents[0]()[1] === 'number') &&
                (typeof parents[0]()[2] === 'number')) {
            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];
        } 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]);
     * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
     *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
     *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
     * </script><pre>
     *
     * @example
     * // Create a segment providing two points.
     *   var p1 = board.create('point', [4.0, 1.0]);
     *   var p2 = board.create('point', [1.0, 1.0]);
     *   var l1 = board.create('segment', [p1, p2]);
     *   var p3 = board.create('point', [4.0, 2.0]);
     *   var p4 = board.create('point', [1.0, 2.0]);
     *   var l2 = board.create('segment', [p3, p4, 3]);
     *   var p5 = board.create('point', [4.0, 3.0]);
     *   var p6 = board.create('point', [1.0, 4.0]);
     *   var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]);
     * </pre><div id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var slex2_p1 = slex1_board.create('point', [4.0, 1.0]);
     *   var slex2_p2 = slex1_board.create('point', [1.0, 1.0]);
     *   var slex2_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
     *   var slex2_p3 = slex1_board.create('point', [4.0, 2.0]);
     *   var slex2_p4 = slex1_board.create('point', [1.0, 2.0]);
     *   var slex2_l2 = slex1_board.create('segment', [slex1_p3, slex1_p4, 3]);
     *   var slex2_p5 = slex1_board.create('point', [4.0, 2.0]);
     *   var slex2_p6 = slex1_board.create('point', [1.0, 2.0]);
     *   var slex2_l3 = slex1_board.create('segment', [slex1_p5, slex1_p6, function(){ return slex2_l1.L();}]);
     * </script><pre>
     *
     */
    JXG.createSegment = function (board, parents, attributes) {
        var el, i, 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.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 <tt>a*x+b*y+c*z = 0</tt>.
     * @see Line
     * @example
     * // Create an arrow providing two points.
     *   var p1 = board.create('point', [4.5, 2.0]);
     *   var p2 = board.create('point', [1.0, 1.0]);
     *   var l1 = board.create('arrow', [p1, p2]);
     * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
     *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
     *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
     * </script><pre>
     */
    JXG.createArrow = function (board, parents, attributes) {
        var el;

        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 <tt>a*x+b*y+c*z = 0</tt>.
     * @example
     * // Create an axis providing two coord pairs.
     *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
     * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
     * </script><pre>
     */
    JXG.createAxis = function (board, parents, attributes) {
        var attr, 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 = Type.copyAttributes(attributes, board.options, 'axis', 'ticks');
            if (Type.exists(attr.ticksdistance)) {
                dist = attr.ticksdistance;
            } else if (Type.isArray(attr.ticks)) {
                dist = attr.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);

            el.defaultTicks.dump = false;

            el.elType = 'axis';
            el.subs = {
                ticks: el.defaultTicks
            };
        } else {
            throw new Error("JSXGraph: Can't create axis with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
        }

        return el;
    };

    JXG.registerElement('axis', JXG.createAxis);

    /**
     * @class With the element tangent the slope of a line, circle, or curve in a certain point can be visualized. A tangent is always constructed
     * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve.
     * @pseudo
     * @description
     * @name Tangent
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Glider} g A glider on a line, circle, or curve.
     * @example
     * // Create a tangent providing a glider on a function graph
     *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
     *   var g1 = board.create('glider', [0.6, 1.2, c1]);
     *   var t1 = board.create('tangent', [g1]);
     * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
     *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
     *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
     *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
     * </script><pre>
     */
    JXG.createTangent = function (board, parents, attributes) {
        var p, c, g, f, i, 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 (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.parents = [];
        for (i = 0; i < parents.length; i++) {
            tangent.parents.push(parents[i].id);
        }

        return tangent;
    };

    /**
     * Register the element type tangent at JSXGraph
     * @private
     */
    JXG.registerElement('tangent', JXG.createTangent);
    JXG.registerElement('polar', JXG.createTangent);

    return {
        Line: JXG.Line,
        createLine: JXG.createLine,
        createTangent: JXG.createTangent,
        createPolar: JXG.createTangent,
        createSegment: JXG.createSegment,
        createAxis: JXG.createAxis,
        createArrow: JXG.createArrow
    };
});
