      1 /*
      2     Copyright 2008-2015
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  base/constants
     39  base/coords
     40  math/math
     41  options
     42  parser/geonext
     43  utils/event
     44  utils/color
     45  utils/type
     46  */
     47
     48 define([
     49     'jxg', 'base/constants', 'base/coords', 'math/math', 'math/statistics', 'options', 'parser/geonext', 'utils/event', 'utils/color', 'utils/type'
     50 ], function (JXG, Const, Coords, Mat, Statistics, Options, GeonextParser, EventEmitter, Color, Type) {
     51
     52     "use strict";
     53
     54     /**
     55      * Constructs a new GeometryElement object.
     56      * @class This is the basic class for geometry elements like points, circles and lines.
     57      * @constructor
     58      * @param {JXG.Board} board Reference to the board the element is constructed on.
     59      * @param {Object} attributes Hash of attributes and their values.
     60      * @param {Number} type Element type (a <tt>JXG.OBJECT_TYPE_</tt> value).
     61      * @param {Number} oclass The element's class (a <tt>JXG.OBJECT_CLASS_</tt> value).
     62      * @borrows JXG.EventEmitter#on as this.on
     63      * @borrows JXG.EventEmitter#off as this.off
     64      * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
     65      * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
     66      */
     67     JXG.GeometryElement = function (board, attributes, type, oclass) {
     68         var name, key, attr;
     69
     70         /**
     71          * Controls if updates are necessary
     72          * @type Boolean
     73          * @default true
     74          */
     75         this.needsUpdate = true;
     76
     77         /**
     78          * Controls if this element can be dragged. In GEONExT only
     79          * free points and gliders can be dragged.
     80          * @type Boolean
     81          * @default false
     82          */
     83         this.isDraggable = false;
     84
     85         /**
     86          * If element is in two dimensional real space this is true, else false.
     87          * @type Boolean
     88          * @default true
     89          */
     90         this.isReal = true;
     91
     92         /**
     93          * Stores all dependent objects to be updated when this point is moved.
     94          * @type Object
     95          */
     96         this.childElements = {};
     97
     98         /**
     99          * If element has a label subelement then this property will be set to true.
    100          * @type Boolean
    101          * @default false
    102          */
    103         this.hasLabel = false;
    104
    105         /**
    106          * True, if the element is currently highlighted.
    107          * @type Boolean
    108          * @default false
    109          */
    110         this.highlighted = false;
    111
    112         /**
    113          * Stores all Intersection Objects which in this moment are not real and
    114          * so hide this element.
    115          * @type Object
    116          */
    117         this.notExistingParents = {};
    118
    119         /**
    120          * Keeps track of all objects drawn as part of the trace of the element.
    121          * @see JXG.GeometryElement#traced
    122          * @see JXG.GeometryElement#clearTrace
    123          * @see JXG.GeometryElement#numTraces
    124          * @type Object
    125          */
    126         this.traces = {};
    127
    128         /**
    129          * Counts the number of objects drawn as part of the trace of the element.
    130          * @see JXG.GeometryElement#traced
    131          * @see JXG.GeometryElement#clearTrace
    132          * @see JXG.GeometryElement#traces
    133          * @type Number
    134          */
    135         this.numTraces = 0;
    136
    137         /**
    138          * Stores the  transformations which are applied during update in an array
    139          * @type Array
    140          * @see JXG.Transformation
    141          */
    142         this.transformations = [];
    143
    144         /**
    145          * @type JXG.GeometryElement
    146          * @default null
    147          * @private
    148          */
    149         this.baseElement = null;
    150
    151         /**
    152          * Elements depending on this element are stored here.
    153          * @type Object
    154          */
    155         this.descendants = {};
    156
    157         /**
    158          * Elements on which this element depends on are stored here.
    159          * @type Object
    160          */
    161         this.ancestors = {};
    162
    163         /**
    164          * Ids of elements on which this element depends directly are stored here.
    165          * @type Object
    166          */
    167         this.parents = [];
    168
    169         /**
    170          * Stores variables for symbolic computations
    171          * @type Object
    172          */
    173         this.symbolic = {};
    174
    175         /**
    176          * Stores the rendering node for the element.
    177          * @type Object
    178          */
    179         this.rendNode = null;
    180
    181         /**
    182          * The string used with {@link JXG.Board#create}
    183          * @type String
    184          */
    185         this.elType = '';
    186
    187         /**
    188          * The element is saved with an explicit entry in the file (<tt>true</tt>) or implicitly
    189          * via a composition.
    190          * @type Boolean
    191          * @default true
    192          */
    193         this.dump = true;
    194
    195         /**
    196          * Subs contains the subelements, created during the create method.
    197          * @type Object
    198          */
    199         this.subs = {};
    200
    201         /**
    202          * The position of this element inside the {@link JXG.Board#objectsList}.
    203          * @type {Number}
    204          * @default -1
    205          * @private
    206          */
    207         this._pos = -1;
    208
    209         /**
    210          * [c,b0,b1,a,k,r,q0,q1]
    211          *
    212          * See
    213          * A.E. Middleditch, T.W. Stacey, and S.B. Tor:
    214          * "Intersection Algorithms for Lines and Circles",
    215          * ACM Transactions on Graphics, Vol. 8, 1, 1989, pp 25-40.
    216          *
    217          * The meaning of the parameters is:
    218          * Circle: points p=[p0,p1] on the circle fulfill
    219          *  a<p,p> + <b,p> + c = 0
    220          * For convenience we also store
    221          *  r: radius
    222          *  k: discriminant = sqrt(<b,b>-4ac)
    223          *  q=[q0,q1] center
    224          *
    225          * Points have radius = 0.
    226          * Lines have radius = infinity.
    227          * b: normalized vector, representing the direction of the line.
    228          *
    229          * Should be put into Coords, when all elements possess Coords.
    230          * @type Array
    231          * @default [1, 0, 0, 0, 1, 1, 0, 0]
    232          */
    233         this.stdform = [1, 0, 0, 0, 1, 1, 0, 0];
    234
    235         /**
    236          * The methodMap determines which methods can be called from within JessieCode and under which name it
    237          * can be used. The map is saved in an object, the name of a property is the name of the method used in JessieCode,
    238          * the value of a property is the name of the method in JavaScript.
    239          * @type Object
    240          */
    241         this.methodMap = {
    242             setLabel: 'setLabelText',
    243             label: 'label',
    244             setName: 'setName',
    245             getName: 'getName',
    246             addTransform: 'addTransform',
    247             setProperty: 'setAttribute',
    248             setAttribute: 'setAttribute',
    249             addChild: 'addChild',
    250             animate: 'animate',
    251             on: 'on',
    252             off: 'off',
    253             trigger: 'trigger'
    254         };
    255
    256         /**
    257          * Quadratic form representation of circles (and conics)
    258          * @type Array
    259          * @default [[1,0,0],[0,1,0],[0,0,1]]
    260          */
    261         this.quadraticform = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
    262
    263         /**
    264          * An associative array containing all visual properties.
    265          * @type Object
    266          * @default empty object
    267          */
    268         this.visProp = {};
    269
    270         EventEmitter.eventify(this);
    271
    272         /**
    273          * Is the mouse over this element?
    274          * @type Boolean
    275          * @default false
    276          */
    277         this.mouseover = false;
    278
    279         /**
    280          * Time stamp containing the last time this element has been dragged.
    281          * @type Date
    282          * @default creation time
    283          */
    284         this.lastDragTime = new Date();
    285
    286         if (arguments.length > 0) {
    287             /**
    288              * Reference to the board associated with the element.
    289              * @type JXG.Board
    290              */
    291             this.board = board;
    292
    293             /**
    294              * Type of the element.
    295              * @constant
    296              * @type number
    297              */
    298             this.type = type;
    299
    300             /**
    301              * Original type of the element at construction time. Used for removing glider property.
    302              * @constant
    303              * @type number
    304              */
    305             this._org_type = type;
    306
    307             /**
    308              * The element's class.
    309              * @constant
    310              * @type number
    311              */
    312             this.elementClass = oclass || Const.OBJECT_CLASS_OTHER;
    313
    314             /**
    315              * Unique identifier for the element. Equivalent to id-attribute of renderer element.
    316              * @type String
    317              */
    318             this.id = attributes.id;
    319
    320             name = attributes.name;
    321             /* If name is not set or null or even undefined, generate an unique name for this object */
    322             if (!Type.exists(name)) {
    323                 name = this.board.generateName(this);
    324             }
    325
    326             if (name !== '') {
    327                 this.board.elementsByName[name] = this;
    328             }
    329
    330             /**
    331              * Not necessarily unique name for the element.
    332              * @type String
    333              * @default Name generated by {@link JXG.Board#generateName}.
    334              * @see JXG.Board#generateName
    335              */
    336             this.name = name;
    337
    338             this.needsRegularUpdate = attributes.needsregularupdate;
    339
    340             // create this.visPropOld and set default values
    341             Type.clearVisPropOld(this);
    342
    343             attr = this.resolveShortcuts(attributes);
    344             for (key in attr) {
    345                 if (attr.hasOwnProperty(key)) {
    346                     this._set(key, attr[key]);
    347                 }
    348             }
    349
    350             this.visProp.draft = attr.draft && attr.draft.draft;
    351             this.visProp.gradientangle = '270';
    352             this.visProp.gradientsecondopacity = this.visProp.fillopacity;
    353             this.visProp.gradientpositionx = 0.5;
    354             this.visProp.gradientpositiony = 0.5;
    355         }
    356     };
    357
    358     JXG.extend(JXG.GeometryElement.prototype, /** @lends JXG.GeometryElement.prototype */ {
    359         /**
    360          * Add an element as a child to the current element. Can be used to model dependencies between geometry elements.
    361          * @param {JXG.GeometryElement} obj The dependent object.
    362          */
    363         addChild: function (obj) {
    364             var el, el2;
    365
    366             this.childElements[obj.id] = obj;
    367             this.addDescendants(obj);
    368             obj.ancestors[this.id] = this;
    369
    370             for (el in this.descendants) {
    371                 if (this.descendants.hasOwnProperty(el)) {
    372                     this.descendants[el].ancestors[this.id] = this;
    373
    374                     for (el2 in this.ancestors) {
    375                         if (this.ancestors.hasOwnProperty(el2)) {
    376                             this.descendants[el].ancestors[this.ancestors[el2].id] = this.ancestors[el2];
    377                         }
    378                     }
    379                 }
    380             }
    381
    382             for (el in this.ancestors) {
    383                 if (this.ancestors.hasOwnProperty(el)) {
    384                     for (el2 in this.descendants) {
    385                         if (this.descendants.hasOwnProperty(el2)) {
    386                             this.ancestors[el].descendants[this.descendants[el2].id] = this.descendants[el2];
    387                         }
    388                     }
    389                 }
    390             }
    391             return this;
    392         },
    393
    394         /**
    395          * Adds the given object to the descendants list of this object and all its child objects.
    396          * @param {JXG.GeometryElement} obj The element that is to be added to the descendants list.
    397          * @private
    398          * @return
    399          */
    400         addDescendants: function (obj) {
    401             var el;
    402
    403             this.descendants[obj.id] = obj;
    404             for (el in obj.childElements) {
    405                 if (obj.childElements.hasOwnProperty(el)) {
    406                     this.addDescendants(obj.childElements[el]);
    407                 }
    408             }
    409             return this;
    410         },
    411
    412         /**
    413          * Adds ids of elements to the array this.parents.
    414          * @param {Array} parents Array of elements or ids of elements.
    415          * Alternatively, one can give a list of objects as parameters.
    416          * @returns {JXG.Object} reference to the object itself.
    417          **/
    418         addParents: function (parents) {
    419             var i, len, par;
    420
    421             if (Type.isArray(parents)) {
    422                 par = parents;
    423             } else {
    424                 par = arguments;
    425             }
    426
    427             len = par.length;
    428             for (i = 0; i < len; ++i) {
    429                 if (Type.isId(par[i])) {
    430                     this.parents.push(par[i]);
    431                 } else if (Type.exists(par[i].id)) {
    432                     this.parents.push(par[i].id);
    433                 }
    434             }
    435
    436             this.parents = Type.uniqueArray(this.parents);
    437         },
    438
    439         /**
    440          * Remove an element as a child from the current element.
    441          * @param {JXG.GeometryElement} obj The dependent object.
    442          */
    443         removeChild: function (obj) {
    444             var el, el2;
    445
    446             delete this.childElements[obj.id];
    447             this.removeDescendants(obj);
    448             delete obj.ancestors[this.id];
    449
    450             /*
    451              // I do not know if these addDescendants stuff has to be adapted to removeChild. A.W.
    452             for (el in this.descendants) {
    453                 if (this.descendants.hasOwnProperty(el)) {
    454                     delete this.descendants[el].ancestors[this.id];
    455
    456                     for (el2 in this.ancestors) {
    457                         if (this.ancestors.hasOwnProperty(el2)) {
    458                             this.descendants[el].ancestors[this.ancestors[el2].id] = this.ancestors[el2];
    459                         }
    460                     }
    461                 }
    462             }
    463
    464             for (el in this.ancestors) {
    465                 if (this.ancestors.hasOwnProperty(el)) {
    466                     for (el2 in this.descendants) {
    467                         if (this.descendants.hasOwnProperty(el2)) {
    468                             this.ancestors[el].descendants[this.descendants[el2].id] = this.descendants[el2];
    469                         }
    470                     }
    471                 }
    472             }
    473             */
    474             return this;
    475         },
    476
    477         /**
    478          * Removes the given object from the descendants list of this object and all its child objects.
    479          * @param {JXG.GeometryElement} obj The element that is to be removed from the descendants list.
    480          * @private
    481          * @return
    482          */
    483         removeDescendants: function (obj) {
    484             var el;
    485
    486             delete this.descendants[obj.id];
    487             for (el in obj.childElements) {
    488                 if (obj.childElements.hasOwnProperty(el)) {
    489                     this.removeDescendants(obj.childElements[el]);
    490                 }
    491             }
    492             return this;
    493         },
    494
    495         /**
    496          * Counts the direct children of an object without counting labels.
    497          * @private
    498          * @return {number} Number of children
    499          */
    500         countChildren: function () {
    501             var prop, d,
    502                 s = 0;
    503
    504             d = this.childElements;
    505             for (prop in d) {
    506                 if (d.hasOwnProperty(prop) && prop.indexOf('Label') < 0) {
    507                     s++;
    508                 }
    509             }
    510             return s;
    511         },
    512
    513         /**
    514          * Returns the elements name, Used in JessieCode.
    515          * @returns {String}
    516          */
    517         getName: function () {
    518             return this.name;
    519         },
    520
    521         /**
    522          * Add transformations to this element.
    523          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
    524          * @returns {JXG.GeometryElement} Reference to the element.
    525          */
    526         addTransform: function (transform) {
    527             return this;
    528         },
    529
    530         /**
    531          * Decides whether an element can be dragged. This is used in {@link JXG.GeometryElement#setPositionDirectly} methods
    532          * where all parent elements are checked if they may be dragged, too.
    533          * @private
    534          * @return {boolean}
    535          */
    536         draggable: function () {
    537             return this.isDraggable && !this.visProp.fixed &&
    538                 !this.visProp.frozen && this.type !== Const.OBJECT_TYPE_GLIDER;
    539         },
    540
    541         /**
    542          * Translates the object by <tt>(x, y)</tt>. In case the element is defined by points, the defining points are
    543          * translated, e.g. a circle constructed by a center point and a point on the circle line.
    544          * @param {Number} method The type of coordinates used here.
    545          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    546          * @param {Array} coords array of translation vector.
    547          * @returns {JXG.GeometryElement} Reference to the element object.
    548          */
    549         setPosition: function (method, coords) {
    550             var parents = [], el, i, len, t;
    551
    552             if (!JXG.exists(this.parents)) {
    553                 return this;
    554             }
    555
    556             len = this.parents.length;
    557             for (i = 0; i < len; ++i) {
    558                 el = this.board.select(this.parents[i]);
    559                 if (Type.isPoint(el)) {
    560                     if (!el.draggable()) {
    561                         return this;
    562                     } else {
    563                         parents.push(el);
    564                     }
    565                 }
    566             }
    567
    568             if (coords.length === 3) {
    569                 coords = coords.slice(1);
    570             }
    571
    572             t = this.board.create('transform', coords, {type: 'translate'});
    573
    574             // We distinguish two cases:
    575             // 1) elements which depend on free elements, i.e. arcs and sectors
    576             // 2) other elements
    577             //
    578             // In the first case we simply transform the parents elements
    579             // In the second case we add a transform to the element.
    580             //
    581             len = parents.length;
    582             if (len > 0) {
    583                 t.applyOnce(parents);
    584             } else {
    585                 if (this.transformations.length > 0 &&
    586                         this.transformations[this.transformations.length - 1].isNumericMatrix) {
    587                     this.transformations[this.transformations.length - 1].melt(t);
    588                 } else {
    589                     this.addTransform(t);
    590                 }
    591             }
    592
    593             /*
    594              * If - against the default configuration - defining gliders are marked as
    595              * draggable, then their position has to be updated now.
    596              */
    597             for (i = 0; i < len; ++i) {
    598                 if (parents[i].type === Const.OBJECT_TYPE_GLIDER) {
    599                     parents[i].updateGlider();
    600                 }
    601             }
    602
    603             return this;
    604         },
    605
    606         /**
    607          * Moves an by the difference of two coordinates.
    608          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    609          * @param {Array} coords coordinates in screen/user units
    610          * @param {Array} oldcoords previous coordinates in screen/user units
    611          * @returns {JXG.GeometryElement} this element
    612          */
    613         setPositionDirectly: function (method, coords, oldcoords) {
    614             var c = new Coords(method, coords, this.board, false),
    615                 oldc = new Coords(method, oldcoords, this.board, false),
    616                 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
    617
    618             this.setPosition(Const.COORDS_BY_USER, dc);
    619
    620             return this;
    621         },
    622
    623         /**
    624          * Array of strings containing the polynomials defining the element.
    625          * Used for determining geometric loci the groebner way.
    626          * @returns {Array} An array containing polynomials describing the locus of the current object.
    627          * @public
    628          */
    629         generatePolynomial: function () {
    630             return [];
    631         },
    632
    633         /**
    634          * Animates properties for that object like stroke or fill color, opacity and maybe
    635          * even more later.
    636          * @param {Object} hash Object containing properties with target values for the animation.
    637          * @param {number} time Number of milliseconds to complete the animation.
    638          * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li></ul>
    639          * @returns {JXG.GeometryElement} A reference to the object
    640          */
    641         animate: function (hash, time, options) {
    642             options = options || {};
    643             var r, p, i,
    644                 delay = this.board.attr.animationdelay,
    645                 steps = Math.ceil(time / delay),
    646                 self = this,
    647
    648                 animateColor = function (startRGB, endRGB, property) {
    649                     var hsv1, hsv2, sh, ss, sv;
    650                     hsv1 = Color.rgb2hsv(startRGB);
    651                     hsv2 = Color.rgb2hsv(endRGB);
    652
    653                     sh = (hsv2[0] - hsv1[0]) / steps;
    654                     ss = (hsv2[1] - hsv1[1]) / steps;
    655                     sv = (hsv2[2] - hsv1[2]) / steps;
    656                     self.animationData[property] = [];
    657
    658                     for (i = 0; i < steps; i++) {
    659                         self.animationData[property][steps - i - 1] = Color.hsv2rgb(hsv1[0] + (i + 1) * sh, hsv1[1] + (i + 1) * ss, hsv1[2] + (i + 1) * sv);
    660                     }
    661                 },
    662
    663                 animateFloat = function (start, end, property, round) {
    664                     var tmp, s;
    665
    666                     start = parseFloat(start);
    667                     end = parseFloat(end);
    668
    669                     // we can't animate without having valid numbers.
    670                     // And parseFloat returns NaN if the given string doesn't contain
    671                     // a valid float number.
    672                     if (isNaN(start) || isNaN(end)) {
    673                         return;
    674                     }
    675
    676                     s = (end - start) / steps;
    677                     self.animationData[property] = [];
    678
    679                     for (i = 0; i < steps; i++) {
    680                         tmp = start + (i + 1) * s;
    681                         self.animationData[property][steps - i - 1] = round ? Math.floor(tmp) : tmp;
    682                     }
    683                 };
    684
    685             this.animationData = {};
    686
    687             for (r in hash) {
    688                 if (hash.hasOwnProperty(r)) {
    689                     p = r.toLowerCase();
    690
    691                     switch (p) {
    692                     case 'strokecolor':
    693                     case 'fillcolor':
    694                         animateColor(this.visProp[p], hash[r], p);
    695                         break;
    696                     case 'size':
    697                         if (!Type.isPoint(this)) {
    698                             break;
    699                         }
    700                         animateFloat(this.visProp[p], hash[r], p, true);
    701                         break;
    702                     case 'strokeopacity':
    703                     case 'strokewidth':
    704                     case 'fillopacity':
    705                         animateFloat(this.visProp[p], hash[r], p, false);
    706                         break;
    707                     }
    708                 }
    709             }
    710
    711             this.animationCallback = options.callback;
    712             this.board.addAnimation(this);
    713             return this;
    714         },
    715
    716         /**
    717          * General update method. Should be overwritten by the element itself.
    718          * Can be used sometimes to commit changes to the object.
    719          */
    720         update: function () {
    721             if (this.visProp.trace) {
    722                 this.cloneToBackground();
    723             }
    724             return this;
    725         },
    726
    727         /**
    728          * Provide updateRenderer method.
    729          * @private
    730          */
    731         updateRenderer: function () {
    732             return this;
    733         },
    734
    735         /**
    736          * Hide the element. It will still exist but not visible on the board.
    737          */
    738         hideElement: function () {
    739             this.visProp.visible = false;
    740             this.board.renderer.hide(this);
    741
    742             if (Type.exists(this.label) && this.hasLabel) {
    743                 this.label.hiddenByParent = true;
    744                 if (this.label.visProp.visible) {
    745                     this.label.hideElement();
    746                 }
    747             }
    748             return this;
    749         },
    750
    751         /**
    752          * Make the element visible.
    753          */
    754         showElement: function () {
    755             this.visProp.visible = true;
    756             this.board.renderer.show(this);
    757
    758             if (Type.exists(this.label) && this.hasLabel && this.label.hiddenByParent) {
    759                 this.label.hiddenByParent = false;
    760                 if (!this.label.visProp.visible) {
    761                     this.label.showElement().updateRenderer();
    762                 }
    763             }
    764             return this;
    765         },
    766
    767         /**
    768          * Sets the value of property <tt>property</tt> to <tt>value</tt>.
    769          * @param {String} property The property's name.
    770          * @param value The new value
    771          * @private
    772          */
    773         _set: function (property, value) {
    774             property = property.toLocaleLowerCase();
    775
    776             // Search for entries in visProp with "color" as part of the property name
    777             // and containing a RGBA string
    778             if (this.visProp.hasOwnProperty(property) && property.indexOf('color') >= 0 &&
    779                     Type.isString(value) && value.length === 9 && value.charAt(0) === '#') {
    780                 value = Color.rgba2rgbo(value);
    781                 this.visProp[property] = value[0];
    782                 // Previously: *=. But then, we can only decrease opacity.
    783                 this.visProp[property.replace('color', 'opacity')] = value[1];
    784             } else {
    785                 this.visProp[property] = value;
    786             }
    787         },
    788
    789         /**
    790          * Resolves property shortcuts like <tt>color</tt> and expands them, e.g. <tt>strokeColor</tt> and <tt>fillColor</tt>.
    791          * Writes the expanded properties back to the given <tt>properties</tt>.
    792          * @param {Object} properties
    793          * @returns {Object} The given parameter with shortcuts expanded.
    794          */
    795         resolveShortcuts: function (properties) {
    796             var key, i;
    797
    798             for (key in Options.shortcuts) {
    799                 if (Options.shortcuts.hasOwnProperty(key)) {
    800                     if (Type.exists(properties[key])) {
    801                         for (i = 0; i < Options.shortcuts[key].length; i++) {
    802                             if (!Type.exists(properties[Options.shortcuts[key][i]])) {
    803                                 properties[Options.shortcuts[key][i]] = properties[key];
    804                             }
    805                         }
    806                     }
    807                 }
    808             }
    809             return properties;
    810         },
    811
    812         /**
    813          * Updates the element's label text, strips all html.
    814          * @param {String} str
    815          */
    816         setLabelText: function (str) {
    817
    818             if (Type.exists(this.label)) {
    819                 str = str.replace(/</g, '<').replace(/>/g, '>');
    820                 this.label.setText(str);
    821             }
    822
    823             return this;
    824         },
    825
    826         /**
    827          * Updates the element's label text and the element's attribute "name", strips all html.
    828          * @param {String} str
    829          */
    830         setName: function (str) {
    831             str = str.replace(/</g, '<').replace(/>/g, '>');
    832             if (this.elType !== 'slider') {
    833                 this.setLabelText(str);
    834             }
    835             this.setAttribute({name: str});
    836         },
    837
    838         /**
    839          * Deprecated alias for {@link JXG.GeometryElement#setAttribute}.
    840          * @deprecated Use {@link JXG.GeometryElement#setAttribute}.
    841          */
    842         setProperty: JXG.shortcut(JXG.GeometryElement.prototype, 'setAttribute'),
    843
    844         /**
    845          * Sets an arbitrary number of attributes.
    846          * @param {Object} attributes An object with attributes.
    847          * @function
    848          * @example
    849          * // Set property directly on creation of an element using the attributes object parameter
    850          * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 5, 5, 1]};
    851          * var p = board.create('point', [2, 2], {visible: false});
    852          *
    853          * // Now make this point visible and fixed:
    854          * p.setAttribute({
    855          *     fixed: true,
    856          *     visible: true
    857          * });
    858          */
    859         setAttribute: function (attributes) {
    860             var i, key, value, arg, opacity, pair, oldvalue,
    861                 properties = {},
    862                 makeTicksFunction = function (v) {
    863                     return function (i) {
    864                         return v;
    865                     };
    866                 };
    867
    868             // normalize the user input
    869             for (i = 0; i < arguments.length; i++) {
    870                 arg = arguments[i];
    871                 if (Type.isString(arg)) {
    872                     // pairRaw is string of the form 'key:value'
    873                     pair = arg.split(':');
    874                     properties[Type.trim(pair[0])] = Type.trim(pair[1]);
    875                 } else if (!Type.isArray(arg)) {
    876                     // pairRaw consists of objects of the form {key1:value1,key2:value2,...}
    877                     JXG.extend(properties, arg);
    878                 } else {
    879                     // pairRaw consists of array [key,value]
    880                     properties[arg[0]] = arg[1];
    881                 }
    882             }
    883
    884             // handle shortcuts
    885             properties = this.resolveShortcuts(properties);
    886
    887             for (i in properties) {
    888                 if (properties.hasOwnProperty(i)) {
    889                     key = i.replace(/\s+/g, '').toLowerCase();
    890                     value = properties[i];
    891                     oldvalue = this.visProp[key];
    892
    893                     switch (key) {
    894                     case 'name':
    895                         oldvalue = this.name;
    896                         delete this.board.elementsByName[this.name];
    897                         this.name = value;
    898                         this.board.elementsByName[this.name] = this;
    899                         break;
    900                     case 'needsregularupdate':
    901                         this.needsRegularUpdate = !(value === 'false' || value === false);
    902                         this.board.renderer.setBuffering(this, this.needsRegularUpdate ? 'auto' : 'static');
    903                         break;
    904                     case 'labelcolor':
    905                         value = Color.rgba2rgbo(value);
    906                         opacity = value[1];
    907                         value = value[0];
    908                         if (opacity === 0) {
    909                             if (Type.exists(this.label) && this.hasLabel) {
    910                                 this.label.hideElement();
    911                             }
    912                         }
    913                         if (Type.exists(this.label) && this.hasLabel) {
    914                             this.label.visProp.strokecolor = value;
    915                             this.board.renderer.setObjectStrokeColor(this.label, value, opacity);
    916                         }
    917                         if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
    918                             this.visProp.strokecolor = value;
    919                             this.visProp.strokeopacity = opacity;
    920                             this.board.renderer.setObjectStrokeColor(this, this.visProp.strokecolor, this.visProp.strokeopacity);
    921                         }
    922                         break;
    923                     case 'infoboxtext':
    924                         if (typeof value === 'string') {
    925                             this.infoboxText = value;
    926                         } else {
    927                             this.infoboxText = false;
    928                         }
    929                         break;
    930                     case 'visible':
    931                         if (value === 'false' || value === false) {
    932                             this.visProp.visible = false;
    933                             this.hideElement();
    934                         } else if (value === 'true' || value === true) {
    935                             this.visProp.visible = true;
    936                             this.showElement();
    937                         }
    938                         break;
    939                     case 'face':
    940                         if (Type.isPoint(this)) {
    941                             this.visProp.face = value;
    942                             this.board.renderer.changePointStyle(this);
    943                         }
    944                         break;
    945                     case 'trace':
    946                         if (value === 'false' || value === false) {
    947                             this.clearTrace();
    948                             this.visProp.trace = false;
    949                         } else {
    950                             this.visProp.trace = true;
    951                         }
    952                         break;
    953                     case 'gradient':
    954                         this.visProp.gradient = value;
    955                         this.board.renderer.setGradient(this);
    956                         break;
    957                     case 'gradientsecondcolor':
    958                         value = Color.rgba2rgbo(value);
    959                         this.visProp.gradientsecondcolor = value[0];
    960                         this.visProp.gradientsecondopacity = value[1];
    961                         this.board.renderer.updateGradient(this);
    962                         break;
    963                     case 'gradientsecondopacity':
    964                         this.visProp.gradientsecondopacity = value;
    965                         this.board.renderer.updateGradient(this);
    966                         break;
    967                     case 'withlabel':
    968                         this.visProp.withlabel = value;
    969                         if (!value) {
    970                             if (this.label && this.hasLabel) {
    971                                 this.label.hideElement();
    972                             }
    973                         } else {
    974                             if (this.label) {
    975                                 if (this.visProp.visible) {
    976                                     this.label.showElement();
    977                                 }
    978                             } else {
    979                                 this.createLabel();
    980                                 if (!this.visProp.visible) {
    981                                     this.label.hideElement();
    982                                 }
    983                             }
    984                         }
    985                         this.hasLabel = value;
    986                         break;
    987                     case 'radius':
    988                         if (this.type === Const.OBJECT_TYPE_ANGLE || this.type === Const.OBJECT_TYPE_SECTOR) {
    989                             this.setRadius(value);
    990                         }
    991                         break;
    992                     case 'rotate':
    993                         if ((this.elementClass === Const.OBJECT_CLASS_TEXT && this.visProp.display === 'internal') ||
    994                                 this.type === Const.OBJECT_TYPE_IMAGE) {
    995                             this.addRotation(value);
    996                         }
    997                         break;
    998                     case 'ticksdistance':
    999                         if (this.type === Const.OBJECT_TYPE_TICKS && typeof value === 'number') {
    1000                             this.ticksFunction = makeTicksFunction(value);
    1001                         }
    1002                         break;
    1003                     case 'generatelabelvalue':
    1004                         if (this.type === Const.OBJECT_TYPE_TICKS && typeof value === 'function') {
    1005                             this.generateLabelValue = value;
    1006                         }
    1007                         break;
    1008                     case 'onpolygon':
    1009                         if (this.type === Const.OBJECT_TYPE_GLIDER) {
    1010                             this.onPolygon = !!value;
    1011                         }
    1012                         break;
    1013                     default:
    1014                         if (Type.exists(this.visProp[key]) && (!JXG.Validator[key] || (JXG.Validator[key] &&
    1015                                 JXG.Validator[key](value)) || (JXG.Validator[key] &&
    1016                                 Type.isFunction(value) && JXG.Validator[key](value())))) {
    1017                             value = value.toLowerCase && value.toLowerCase() === 'false' ? false : value;
    1018                             this._set(key, value);
    1019                         }
    1020                         break;
    1021                     }
    1022                     this.triggerEventHandlers(['attribute:' + key], [oldvalue, value, this]);
    1023                 }
    1024             }
    1025
    1026             this.triggerEventHandlers(['attribute'], [properties, this]);
    1027
    1028             if (!this.visProp.needsregularupdate) {
    1029                 this.board.fullUpdate();
    1030             } else {
    1031                 this.board.update(this);
    1032             }
    1033
    1034             return this;
    1035         },
    1036
    1037         /**
    1038          * Deprecated alias for {@link JXG.GeometryElement#getAttribute}.
    1039          * @deprecated Use {@link JXG.GeometryElement#getAttribute}.
    1040          */
    1041         getProperty: JXG.shortcut(JXG.GeometryElement.prototype, 'getAttribute'),
    1042
    1043         /**
    1044          * Get the value of the property <tt>key</tt>.
    1045          * @param {String} key The name of the property you are looking for
    1046          * @returns The value of the property
    1047          */
    1048         getAttribute: function (key) {
    1049             var result;
    1050             key = key.toLowerCase();
    1051
    1052             switch (key) {
    1053             case 'needsregularupdate':
    1054                 result = this.needsRegularUpdate;
    1055                 break;
    1056             case 'labelcolor':
    1057                 result = this.label.visProp.strokecolor;
    1058                 break;
    1059             case 'infoboxtext':
    1060                 result = this.infoboxText;
    1061                 break;
    1062             case 'withlabel':
    1063                 result = this.hasLabel;
    1064                 break;
    1065             default:
    1066                 result = this.visProp[key];
    1067                 break;
    1068             }
    1069
    1070             return result;
    1071         },
    1072
    1073         /**
    1074          * Set the dash style of an object. See {@link #dash} for a list of available dash styles.
    1075          * You should use {@link #setAttribute} instead of this method.
    1076          * @param {number} dash Indicates the new dash style
    1077          * @private
    1078          */
    1079         setDash: function (dash) {
    1080             this.setAttribute({dash: dash});
    1081             return this;
    1082         },
    1083
    1084         /**
    1085          * Notify all child elements for updates.
    1086          * @private
    1087          */
    1088         prepareUpdate: function () {
    1089             this.needsUpdate = true;
    1090             return this;
    1091         },
    1092
    1093         /**
    1094          * Removes the element from the construction.  This only removes the SVG or VML node of the element and its label (if available) from
    1095          * the renderer, to remove the element completely you should use {@link JXG.Board#removeObject}.
    1096          */
    1097         remove: function () {
    1098             this.board.renderer.remove(this.board.renderer.getElementById(this.id));
    1099
    1100             if (this.hasLabel) {
    1101                 this.board.renderer.remove(this.board.renderer.getElementById(this.label.id));
    1102             }
    1103             return this;
    1104         },
    1105
    1106         /**
    1107          * Returns the coords object where a text that is bound to the element shall be drawn.
    1108          * Differs in some cases from the values that getLabelAnchor returns.
    1109          * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn.
    1110          * @see JXG.GeometryElement#getLabelAnchor
    1111          */
    1112         getTextAnchor: function () {
    1113             return new Coords(Const.COORDS_BY_USER, [0, 0], this.board);
    1114         },
    1115
    1116         /**
    1117          * Returns the coords object where the label of the element shall be drawn.
    1118          * Differs in some cases from the values that getTextAnchor returns.
    1119          * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn.
    1120          * @see JXG.GeometryElement#getTextAnchor
    1121          */
    1122         getLabelAnchor: function () {
    1123             return new Coords(Const.COORDS_BY_USER, [0, 0], this.board);
    1124         },
    1125
    1126         /**
    1127          * Determines whether the element has arrows at start or end of the arc.
    1128          * @param {Boolean} firstArrow True if there is an arrow at the start of the arc, false otherwise.
    1129          * @param {Boolean} lastArrow True if there is an arrow at the end of the arc, false otherwise.
    1130          */
    1131         setArrow: function (firstArrow, lastArrow) {
    1132             this.visProp.firstarrow = firstArrow;
    1133             this.visProp.lastarrow = lastArrow;
    1134             this.prepareUpdate().update();
    1135             return this;
    1136         },
    1137
    1138         /**
    1139          * Creates a gradient nodes in the renderer.
    1140          * @see JXG.SVGRenderer#setGradient
    1141          * @private
    1142          */
    1143         createGradient: function () {
    1144             if (this.visProp.gradient === 'linear' || this.visProp.gradient === 'radial') {
    1145                 this.board.renderer.setGradient(this);
    1146             }
    1147         },
    1148
    1149         /**
    1150          * Creates a label element for this geometry element.
    1151          * @see #addLabelToElement
    1152          */
    1153         createLabel: function () {
    1154             var attr,
    1155                 that = this;
    1156
    1157             // this is a dirty hack to resolve the text-dependency. If there is no text element available,
    1158             // just don't create a label. This method is usually not called by a user, so we won't throw
    1159             // an exception here and simply output a warning via JXG.debug.
    1160             if (JXG.elements.text) {
    1161                 attr =  Type.deepCopy(this.visProp.label, null);
    1162                 attr.id = this.id + 'Label';
    1163                 attr.isLabel = true;
    1164                 attr.visible = this.visProp.visible;
    1165                 attr.anchor = this;
    1166                 attr.priv = this.visProp.priv;
    1167
    1168                 if (this.visProp.withlabel) {
    1169                     this.label = JXG.elements.text(this.board, [0, 0, function () {
    1170                         if (typeof that.name === 'function') {
    1171                             return that.name();
    1172                         }
    1173                         return that.name;
    1174                     }], attr);
    1175                     this.label.needsUpdate = true;
    1176                     this.label.update();
    1177
    1178                     this.label.dump = false;
    1179
    1180                     if (!this.visProp.visible) {
    1181                         this.label.hiddenByParent = true;
    1182                         this.label.visProp.visible = false;
    1183                     }
    1184                     this.hasLabel = true;
    1185                 }
    1186             } else {
    1187                 JXG.debug('JSXGraph: Can\'t create label: text element is not available. Make sure you include base/text');
    1188             }
    1189
    1190             return this;
    1191         },
    1192
    1193         /**
    1194          * Highlights the element.
    1195          * @param {Boolean} [force=false] Force the highlighting
    1196          * @returns {JXG.Board}
    1197          */
    1198         highlight: function (force) {
    1199             force = Type.def(force, false);
    1200             // I know, we have the JXG.Board.highlightedObjects AND JXG.GeometryElement.highlighted and YES we need both.
    1201             // Board.highlightedObjects is for the internal highlighting and GeometryElement.highlighted is for user highlighting
    1202             // initiated by the user, e.g. through custom DOM events. We can't just pick one because this would break user
    1203             // defined highlighting in many ways:
    1204             //  * if overriding the highlight() methods the user had to handle the highlightedObjects stuff, otherwise he'd break
    1205             //    everything (e.g. the pie chart example http://jsxgraph.uni-bayreuth.de/wiki/index.php/Pie_chart (not exactly
    1206             //    user defined but for this type of chart the highlight method was overridden and not adjusted to the changes in here)
    1207             //    where it just kept highlighting until the radius of the pie was far beyond infinity...
    1208             //  * user defined highlighting would get pointless, everytime the user highlights something using .highlight(), it would get
    1209             //    dehighlighted immediately, because highlight puts the element into highlightedObjects and from there it gets dehighlighted
    1210             //    through dehighlightAll.
    1211
    1212             // highlight only if not highlighted
    1213             if (this.visProp.highlight && (!this.highlighted || force)) {
    1214                 this.highlighted = true;
    1215                 this.board.highlightedObjects[this.id] = this;
    1216                 this.board.renderer.highlight(this);
    1217             }
    1218             return this;
    1219         },
    1220
    1221         /**
    1222          * Uses the "normal" properties of the element.
    1223          * @returns {JXG.Board}
    1224          */
    1225         noHighlight: function () {
    1226             // see comment in JXG.GeometryElement.highlight()
    1227
    1228             // dehighlight only if not highlighted
    1229             if (this.highlighted) {
    1230                 this.highlighted = false;
    1231                 delete this.board.highlightedObjects[this.id];
    1232                 this.board.renderer.noHighlight(this);
    1233             }
    1234             return this;
    1235         },
    1236
    1237         /**
    1238          * Removes all objects generated by the trace function.
    1239          */
    1240         clearTrace: function () {
    1241             var obj;
    1242
    1243             for (obj in this.traces) {
    1244                 if (this.traces.hasOwnProperty(obj)) {
    1245                     this.board.renderer.remove(this.traces[obj]);
    1246                 }
    1247             }
    1248
    1249             this.numTraces = 0;
    1250             return this;
    1251         },
    1252
    1253         /**
    1254          * Copy the element to background. This is used for tracing elements.
    1255          * @returns {JXG.GeometryElement} A reference to the element
    1256          */
    1257         cloneToBackground: function () {
    1258             return this;
    1259         },
    1260
    1261         /**
    1262          * Dimensions of the smallest rectangle enclosing the element.
    1263          * @returns {Array} The coordinates of the enclosing rectangle in a format like the bounding box in {@link JXG.Board#setBoundingBox}.
    1264          */
    1265         bounds: function () {
    1266             return [0, 0, 0, 0];
    1267         },
    1268
    1269         /**
    1270          * Normalize the element's standard form.
    1271          * @private
    1272          */
    1273         normalize: function () {
    1274             this.stdform = Mat.normalize(this.stdform);
    1275             return this;
    1276         },
    1277
    1278         /**
    1279          * EXPERIMENTAL. Generate JSON object code of visProp and other properties.
    1280          * @type string
    1281          * @private
    1282          * @ignore
    1283          * @return JSON string containing element's properties.
    1284          */
    1285         toJSON: function () {
    1286             var vis, key,
    1287                 json = ['{"name":', this.name];
    1288
    1289             json.push(', ' + '"id":' + this.id);
    1290
    1291             vis = [];
    1292             for (key in this.visProp) {
    1293                 if (this.visProp.hasOwnProperty(key)) {
    1294                     if (Type.exists(this.visProp[key])) {
    1295                         vis.push('"' + key + '":' + this.visProp[key]);
    1296                     }
    1297                 }
    1298             }
    1299             json.push(', "visProp":{' + vis.toString() + '}');
    1300             json.push('}');
    1301
    1302             return json.join('');
    1303         },
    1304
    1305
    1306         /**
    1307          * Rotate texts or images by a given degree. Works only for texts where JXG.Text#display equal to "internal".
    1308          * @param {number} angle The degree of the rotation (90 means vertical text).
    1309          * @see JXG.GeometryElement#rotate
    1310          */
    1311         addRotation: function (angle) {
    1312             var tOffInv, tOff, tS, tSInv, tRot,
    1313                 that = this;
    1314
    1315             if (((this.elementClass === Const.OBJECT_CLASS_TEXT && this.visProp.display === 'internal') ||
    1316                     this.type === Const.OBJECT_TYPE_IMAGE) && angle !== 0) {
    1317
    1318                 tOffInv = this.board.create('transform', [
    1319                     function () {
    1320                         return -that.X();
    1321                     }, function () {
    1322                         return -that.Y();
    1323                     }
    1324                 ], {type: 'translate'});
    1325
    1326                 tOff = this.board.create('transform', [
    1327                     function () {
    1328                         return that.X();
    1329                     }, function () {
    1330                         return that.Y();
    1331                     }
    1332                 ], {type: 'translate'});
    1333
    1334                 tS = this.board.create('transform', [
    1335                     function () {
    1336                         return that.board.unitX / that.board.unitY;
    1337                     }, function () {
    1338                         return 1;
    1339                     }
    1340                 ], {type: 'scale'});
    1341
    1342                 tSInv = this.board.create('transform', [
    1343                     function () {
    1344                         return that.board.unitY / that.board.unitX;
    1345                     }, function () {
    1346                         return 1;
    1347                     }
    1348                 ], {type: 'scale'});
    1349
    1350                 tRot = this.board.create('transform', [angle * Math.PI / 180], {type: 'rotate'});
    1351
    1352                 tOffInv.bindTo(this);
    1353                 tS.bindTo(this);
    1354                 tRot.bindTo(this);
    1355                 tSInv.bindTo(this);
    1356                 tOff.bindTo(this);
    1357             }
    1358
    1359             return this;
    1360         },
    1361
    1362         /**
    1363          * Set the highlightStrokeColor of an element
    1364          * @param {String} sColor String which determines the stroke color of an object when its highlighted.
    1365          * @see JXG.GeometryElement#highlightStrokeColor
    1366          * @deprecated Use {@link #setAttribute}
    1367          */
    1368         highlightStrokeColor: function (sColor) {
    1369             this.setAttribute({highlightStrokeColor: sColor});
    1370             return this;
    1371         },
    1372
    1373         /**
    1374          * Set the strokeColor of an element
    1375          * @param {String} sColor String which determines the stroke color of an object.
    1376          * @see JXG.GeometryElement#strokeColor
    1377          * @deprecated Use {@link #setAttribute}
    1378          */
    1379         strokeColor: function (sColor) {
    1380             this.setAttribute({strokeColor: sColor});
    1381             return this;
    1382         },
    1383
    1384         /**
    1385          * Set the strokeWidth of an element
    1386          * @param {Number} width Integer which determines the stroke width of an outline.
    1387          * @see JXG.GeometryElement#strokeWidth
    1388          * @deprecated Use {@link #setAttribute}
    1389          */
    1390         strokeWidth: function (width) {
    1391             this.setAttribute({strokeWidth: width});
    1392             return this;
    1393         },
    1394
    1395
    1396         /**
    1397          * Set the fillColor of an element
    1398          * @param {String} fColor String which determines the fill color of an object.
    1399          * @see JXG.GeometryElement#fillColor
    1400          * @deprecated Use {@link #setAttribute}
    1401          */
    1402         fillColor: function (fColor) {
    1403             this.setAttribute({fillColor: fColor});
    1404             return this;
    1405         },
    1406
    1407         /**
    1408          * Set the highlightFillColor of an element
    1409          * @param {String} fColor String which determines the fill color of an object when its highlighted.
    1410          * @see JXG.GeometryElement#highlightFillColor
    1411          * @deprecated Use {@link #setAttribute}
    1412          */
    1413         highlightFillColor: function (fColor) {
    1414             this.setAttribute({highlightFillColor: fColor});
    1415             return this;
    1416         },
    1417
    1418         /**
    1419          * Set the labelColor of an element
    1420          * @param {String} lColor String which determines the text color of an object's label.
    1421          * @see JXG.GeometryElement#labelColor
    1422          * @deprecated Use {@link #setAttribute}
    1423          */
    1424         labelColor: function (lColor) {
    1425             this.setAttribute({labelColor: lColor});
    1426             return this;
    1427         },
    1428
    1429         /**
    1430          * Set the dash type of an element
    1431          * @param {Number} d Integer which determines the way of dashing an element's outline.
    1432          * @see JXG.GeometryElement#dash
    1433          * @deprecated Use {@link #setAttribute}
    1434          */
    1435         dash: function (d) {
    1436             this.setAttribute({dash: d});
    1437             return this;
    1438         },
    1439
    1440         /**
    1441          * Set the visibility of an element
    1442          * @param {Boolean} v Boolean which determines whether the element is drawn.
    1443          * @see JXG.GeometryElement#visible
    1444          * @deprecated Use {@link #setAttribute}
    1445          */
    1446         visible: function (v) {
    1447             this.setAttribute({visible: v});
    1448             return this;
    1449         },
    1450
    1451         /**
    1452          * Set the shadow of an element
    1453          * @param {Boolean} s Boolean which determines whether the element has a shadow or not.
    1454          * @see JXG.GeometryElement#shadow
    1455          * @deprecated Use {@link #setAttribute}
    1456          */
    1457         shadow: function (s) {
    1458             this.setAttribute({shadow: s});
    1459             return this;
    1460         },
    1461
    1462         /**
    1463          * The type of the element as used in {@link JXG.Board#create}.
    1464          * @returns {String}
    1465          */
    1466         getType: function () {
    1467             return this.elType;
    1468         },
    1469
    1470         /**
    1471          * List of the element ids resp. values used as parents in {@link JXG.Board#create}.
    1472          * @returns {Array}
    1473          */
    1474         getParents: function () {
    1475             return Type.isArray(this.parents) ? this.parents : [];
    1476         },
    1477
    1478         /**
    1479          * Snaps the element to the grid. Only works for points, lines and circles. Points will snap to the grid
    1480          * as defined in their properties {@link JXG.Point#snapSizeX} and {@link JXG.Point#snapSizeY}. Lines and circles
    1481          * will snap their parent points to the grid, if they have {@link JXG.Point#snapToGrid} set to true.
    1482          * @returns {JXG.GeometryElement} Reference to the element.
    1483          */
    1484         snapToGrid: function () {
    1485             return this;
    1486         },
    1487
    1488         /**
    1489          * Snaps the element to points. Only works for points. Points will snap to the next point
    1490          * as defined in their properties {@link JXG.Point#attractorDistance} and {@link JXG.Point#attractorUnit}.
    1491          * Lines and circles
    1492          * will snap their parent points to points.
    1493          * @returns {JXG.GeometryElement} Reference to the element.
    1494          */
    1495         snapToPoints: function () {
    1496             return this;
    1497         },
    1498
    1499         /**
    1500          * Retrieve a copy of the current visProp.
    1501          * @returns {Object}
    1502          */
    1503         getAttributes: function () {
    1504             var attributes = Type.deepCopy(this.visProp),
    1505                 cleanThis = ['attractors', 'snatchdistance', 'traceattributes', 'frozen',
    1506                     'shadow', 'gradientangle', 'gradientsecondopacity', 'gradientpositionx', 'gradientpositiony',
    1507                     'needsregularupdate', 'zoom', 'layer', 'offset'],
    1508                 i;
    1509
    1510             attributes.id = this.id;
    1511             attributes.name = this.name;
    1512
    1513             for (i = 0; i < cleanThis.length; i++) {
    1514                 delete attributes[cleanThis[i]];
    1515             }
    1516
    1517             return attributes;
    1518         },
    1519
    1520         /**
    1521          * Checks whether (x,y) is near the element.
    1522          * @param {Number} x Coordinate in x direction, screen coordinates.
    1523          * @param {Number} y Coordinate in y direction, screen coordinates.
    1524          * @returns {Boolean} True if (x,y) is near the element, False otherwise.
    1525          */
    1526         hasPoint: function (x, y) {
    1527             return false;
    1528         },
    1529
    1530         /**
    1531          * Move an element to its nearest grid point.
    1532          * The function uses the coords object of the element as
    1533          * its actual position. If there is no coords object, nothing is done.
    1534          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
    1535          * @returns {JXG.GeometryElement} Reference to this element
    1536          */
    1537         handleSnapToGrid: function (force) {
    1538             var x, y, ticks,
    1539                 sX = this.visProp.snapsizex,
    1540                 sY = this.visProp.snapsizey;
    1541
    1542             if (!JXG.exists(this.coords)) {
    1543                 return this;
    1544             }
    1545
    1546             if (this.visProp.snaptogrid || force === true) {
    1547                 x = this.coords.usrCoords[1];
    1548                 y = this.coords.usrCoords[2];
    1549
    1550                 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
    1551                     ticks = this.board.defaultAxes.x.defaultTicks;
    1552                     sX = ticks.ticksDelta * (ticks.visProp.minorticks + 1);
    1553                 }
    1554
    1555                 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
    1556                     ticks = this.board.defaultAxes.y.defaultTicks;
    1557                     sY = ticks.ticksDelta * (ticks.visProp.minorticks + 1);
    1558                 }
    1559
    1560                 // if no valid snap sizes are available, don't change the coords.
    1561                 if (sX > 0 && sY > 0) {
    1562                     this.coords.setCoordinates(Const.COORDS_BY_USER, [Math.round(x / sX) * sX, Math.round(y / sY) * sY]);
    1563                 }
    1564             }
    1565             return this;
    1566         },
    1567
    1568         /**
    1569          * Alias of {@link JXG.GeometryElement#on}.
    1570          */
    1571         addEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'on'),
    1572
    1573         /**
    1574          * Alias of {@link JXG.GeometryElement#off}.
    1575          */
    1576         removeEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'off'),
    1577
    1578         /* **************************
    1579          *     EVENT DEFINITION
    1580          * for documentation purposes
    1581          * ************************** */
    1582
    1583         //region Event handler documentation
    1584         /**
    1585          * @event
    1586          * @description This event is fired whenever the user is hovering over an element.
    1587          * @name JXG.GeometryElement#over
    1588          * @param {Event} e The browser's event object.
    1589          */
    1590         __evt__over: function (e) { },
    1591
    1592         /**
    1593          * @event
    1594          * @description This event is fired whenever the user puts the mouse over an element.
    1595          * @name JXG.GeometryElement#mouseover
    1596          * @param {Event} e The browser's event object.
    1597          */
    1598         __evt__mouseover: function (e) { },
    1599
    1600         /**
    1601          * @event
    1602          * @description This event is fired whenever the user is leaving an element.
    1603          * @name JXG.GeometryElement#out
    1604          * @param {Event} e The browser's event object.
    1605          */
    1606         __evt__out: function (e) { },
    1607
    1608         /**
    1609          * @event
    1610          * @description This event is fired whenever the user puts the mouse away from an element.
    1611          * @name JXG.GeometryElement#mouseout
    1612          * @param {Event} e The browser's event object.
    1613          */
    1614         __evt__mouseout: function (e) { },
    1615
    1616         /**
    1617          * @event
    1618          * @description This event is fired whenever the user is moving over an element.
    1619          * @name JXG.GeometryElement#move
    1620          * @param {Event} e The browser's event object.
    1621          */
    1622         __evt__move: function (e) { },
    1623
    1624         /**
    1625          * @event
    1626          * @description This event is fired whenever the user is moving the mouse over an element.
    1627          * @name JXG.GeometryElement#mousemove
    1628          * @param {Event} e The browser's event object.
    1629          */
    1630         __evt__mousemove: function (e) { },
    1631
    1632         /**
    1633          * @event
    1634          * @description This event is fired whenever the user drags an element.
    1635          * @name JXG.GeometryElement#drag
    1636          * @param {Event} e The browser's event object.
    1637          */
    1638         __evt__drag: function (e) { },
    1639
    1640         /**
    1641          * @event
    1642          * @description This event is fired whenever the user drags the element with a mouse.
    1643          * @name JXG.GeometryElement#mousedrag
    1644          * @param {Event} e The browser's event object.
    1645          */
    1646         __evt__mousedrag: function (e) { },
    1647
    1648         /**
    1649          * @event
    1650          * @description This event is fired whenever the user drags the element on a touch device.
    1651          * @name JXG.GeometryElement#touchdrag
    1652          * @param {Event} e The browser's event object.
    1653          */
    1654         __evt__touchdrag: function (e) { },
    1655
    1656         /**
    1657          * @event
    1658          * @description Whenever the user starts to touch or click an element.
    1659          * @name JXG.GeometryElement#down
    1660          * @param {Event} e The browser's event object.
    1661          */
    1662         __evt__down: function (e) { },
    1663
    1664         /**
    1665          * @event
    1666          * @description Whenever the user starts to click an element.
    1667          * @name JXG.GeometryElement#mousedown
    1668          * @param {Event} e The browser's event object.
    1669          */
    1670         __evt__mousedown: function (e) { },
    1671
    1672         /**
    1673          * @event
    1674          * @description Whenever the user starts to touch an element.
    1675          * @name JXG.GeometryElement#touchdown
    1676          * @param {Event} e The browser's event object.
    1677          */
    1678         __evt__touchdown: function (e) { },
    1679
    1680         /**
    1681          * @event
    1682          * @description Whenever the user stops to touch or click an element.
    1683          * @name JXG.GeometryElement#up
    1684          * @param {Event} e The browser's event object.
    1685          */
    1686         __evt__up: function (e) { },
    1687
    1688         /**
    1689          * @event
    1690          * @description Whenever the user releases the mousebutton over an element.
    1691          * @name JXG.GeometryElement#mouseup
    1692          * @param {Event} e The browser's event object.
    1693          */
    1694         __evt__mouseup: function (e) { },
    1695
    1696         /**
    1697          * @event
    1698          * @description Whenever the user stops touching an element.
    1699          * @name JXG.GeometryElement#touchup
    1700          * @param {Event} e The browser's event object.
    1701          */
    1702         __evt__touchup: function (e) {},
    1703
    1704         /**
    1705          * @event
    1706          * @description Notify everytime an attribute is changed.
    1707          * @name JXG.GeometryElement#attribute
    1708          * @param {Object} o A list of changed attributes and their new value.
    1709          * @param {Object} el Reference to the element
    1710          */
    1711         __evt__attribute: function (o, el) {},
    1712
    1713         /**
    1714          * @event
    1715          * @description This is a generic event handler. It exists for every possible attribute that can be set for
    1716          * any element, e.g. if you want to be notified everytime an element's strokecolor is changed, is the event
    1717          * <tt>attribute:strokecolor</tt>.
    1718          * @name JXG.GeometryElement#attribute:<attribute>
    1719          * @param val The old value.
    1720          * @param nval The new value
    1721          * @param {Object} el Reference to the element
    1722          */
    1723         __evt__attribute_: function (val, nval, el) {},
    1724
    1725         /**
    1726          * @ignore
    1727          */
    1728         __evt: function () {}
    1729         //endregion
    1730
    1731     });
    1732
    1733     return JXG.GeometryElement;
    1734 });
    1735