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