      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  math/math
     39  base/constants
     40  base/point
     41  utils/type
     42   elements:
     43    point
     44    group
     45    segment
     46    ticks
     47    glider
     48    text
     49  */
     50
     51 /**
     52  * @fileoverview The geometry object slider is defined in this file. Slider stores all
     53  * style and functional properties that are required to draw and use a slider on
     54  * a board.
     55  */
     56
     57 define([
     58     'jxg', 'math/math', 'base/constants', 'utils/type', 'base/point', 'base/group',
     59     'base/element', 'base/line', 'base/ticks', 'base/text'
     60 ], function (JXG, Mat, Const, Type, Point, Group, GeometryElement, Line, Ticks, Text) {
     61
     62     "use strict";
     63
     64     /**
     65      * @class A slider can be used to choose values from a given range of numbers.
     66      * @pseudo
     67      * @description
     68      * @name Slider
     69      * @augments Glider
     70      * @constructor
     71      * @type JXG.Point
     72      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     73      * @param {Array_Array_Array} start,end,data The first two arrays give the start and the end where the slider is drawn
     74      * on the board. The third array gives the start and the end of the range the slider operates as the first resp. the
     75      * third component of the array. The second component of the third array gives its start value.
     76      * @example
     77      * // Create a slider with values between 1 and 10, initial position is 5.
     78      * var s = board.create('slider', [[1, 2], [3, 2], [1, 5, 10]]);
     79      * </pre><div id="cfb51cde-2603-4f18-9cc4-1afb452b374d" style="width: 200px; height: 200px;"></div>
     80      * <script type="text/javascript">
     81      *   (function () {
     82      *     var board = JXG.JSXGraph.initBoard('cfb51cde-2603-4f18-9cc4-1afb452b374d', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     83      *     var s = board.create('slider', [[1, 2], [3, 2], [1, 5, 10]]);
     84      *   })();
     85      * </script><pre>
     86      * @example
     87      * // Create a slider taking integer values between 1 and 50. Initial value is 50.
     88      * var s = board.create('slider', [[1, 3], [3, 1], [1, 10, 50]], {snapWidth: 1});
     89      * </pre><div id="e17128e6-a25d-462a-9074-49460b0d66f4" style="width: 200px; height: 200px;"></div>
     90      * <script type="text/javascript">
     91      *   (function () {
     92      *     var board = JXG.JSXGraph.initBoard('e17128e6-a25d-462a-9074-49460b0d66f4', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     93      *     var s = board.create('slider', [[1, 3], [3, 1], [1, 10, 50]], {snapWidth: 1});
     94      *   })();
     95      * </script><pre>
     96      */
     97     JXG.createSlider = function (board, parents, attributes) {
     98         var pos0, pos1, smin, start, smax, sdiff,
     99             p1, p2, l1, ticks, ti, startx, starty, p3, l2, t,
    100             withText, withTicks, snapWidth, attr, precision, el;
    101
    102         attr = Type.copyAttributes(attributes, board.options, 'slider');
    103         withTicks = attr.withticks;
    104         withText = attr.withlabel;
    105         snapWidth = attr.snapwidth;
    106         precision = attr.precision;
    107
    108         // start point
    109         attr = Type.copyAttributes(attributes, board.options, 'slider', 'point1');
    110         p1 = board.create('point', parents[0],  attr);
    111
    112         // end point
    113         attr = Type.copyAttributes(attributes, board.options, 'slider', 'point2');
    114         p2 = board.create('point', parents[1],  attr);
    115         board.create('group', [p1, p2]);
    116
    117         // slide line
    118         attr = Type.copyAttributes(attributes, board.options, 'slider', 'baseline');
    119         l1 = board.create('segment', [p1, p2], attr);
    120
    121         // this is required for a correct projection of the glider onto the segment below
    122         l1.updateStdform();
    123
    124         pos0 = p1.coords.usrCoords.slice(1);
    125         pos1 = p2.coords.usrCoords.slice(1);
    126         smin = parents[2][0];
    127         start = parents[2][1];
    128         smax = parents[2][2];
    129         sdiff = smax - smin;
    130
    131         if (withTicks) {
    132             attr = Type.copyAttributes(attributes, board.options, 'slider', 'ticks');
    133             ticks  = 2;
    134             ti = board.create('ticks', [
    135                 l1,
    136                 p2.Dist(p1) / ticks,
    137
    138                 function (tick) {
    139                     var dFull = p1.Dist(p2),
    140                         d = p1.coords.distance(Const.COORDS_BY_USER, tick);
    141
    142                     if (dFull < Mat.eps) {
    143                         return 0;
    144                     }
    145
    146                     return d / dFull * sdiff + smin;
    147                 }
    148             ], attr);
    149         }
    150
    151         startx = pos0[0] + (pos1[0] - pos0[0]) * (start - smin) / (smax - smin);
    152         starty = pos0[1] + (pos1[1] - pos0[1]) * (start - smin) / (smax - smin);
    153
    154         // glider point
    155         attr = Type.copyAttributes(attributes, board.options, 'slider');
    156         // overwrite this in any case; the sliders label is a special text element, not the gliders label.
    157         // this will be set back to true after the text was created (and only if withlabel was true initially).
    158         attr.withLabel = false;
    159         // gliders set snapwidth=-1 by default (i.e. deactivate them)
    160         p3 = board.create('glider', [startx, starty, l1], attr);
    161         p3.setAttribute({snapwidth: snapWidth});
    162
    163         // segment from start point to glider point
    164         attr = Type.copyAttributes(attributes, board.options, 'slider', 'highline');
    165         l2 = board.create('segment', [p1, p3],  attr);
    166
    167         /**
    168          * Returns the current slider value.
    169          * @memberOf Slider.prototype
    170          * @name Value
    171          * @returns {Number}
    172          */
    173         p3.Value = function () {
    174             var sdiff = this._smax - this._smin;
    175             return p3.visProp.snapwidth === -1 ? this.position * sdiff + this._smin : Math.round((this.position * sdiff + this._smin) / this.visProp.snapwidth) * this.visProp.snapwidth;
    176         };
    177
    178         p3.methodMap = Type.deepCopy(p3.methodMap, {
    179             Value: 'Value',
    180             smax: '_smax',
    181             smin: '_smin'
    182         });
    183
    184         /**
    185          * End value of the slider range.
    186          * @memberOf Slider.prototype
    187          * @name _smax
    188          * @type Number
    189          */
    190         p3._smax = smax;
    191
    192         /**
    193          * Start value of the slider range.
    194          * @memberOf Slider.prototype
    195          * @name _smin
    196          * @type Number
    197          */
    198         p3._smin = smin;
    199
    200         if (withText) {
    201             attr = Type.copyAttributes(attributes, board.options, 'slider', 'label');
    202             t = board.create('text', [
    203                 function () {
    204                     return (p2.X() - p1.X()) * 0.05 + p2.X();
    205                 },
    206                 function () {
    207                     return (p2.Y() - p1.Y()) * 0.05 + p2.Y();
    208                 },
    209                 function () {
    210                     var n;
    211
    212                     if (p3.name && p3.name !== '') {
    213                         n = p3.name + ' = ';
    214                     } else {
    215                         n = '';
    216                     }
    217
    218                     return n + (p3.Value()).toFixed(precision);
    219                 }
    220             ], attr);
    221
    222             /**
    223              * The text element to the right of the slider, indicating its current value.
    224              * @memberOf Slider.prototype
    225              * @name label
    226              * @type JXG.Text
    227              */
    228             p3.label = t;
    229
    230             // reset the withlabel attribute
    231             p3.visProp.withlabel = true;
    232             p3.hasLabel = true;
    233         }
    234
    235         /**
    236          * Start point of the base line.
    237          * @memberOf Slider.prototype
    238          * @name point1
    239          * @type JXG.Point
    240          */
    241         p3.point1 = p1;
    242         /**
    243          * End point of the base line.
    244          * @memberOf Slider.prototype
    245          * @name point2
    246          * @type JXG.Point
    247          */
    248         p3.point2 = p2;
    249
    250         /**
    251          * The baseline the glider is bound to.
    252          * @memberOf Slider.prototype
    253          * @name baseline
    254          * @type JXG.Line
    255          */
    256         p3.baseline = l1;
    257         /**
    258          * A line on top of the baseline, indicating the slider's progress.
    259          * @memberOf Slider.prototype
    260          * @name highline
    261          * @type JXG.Line
    262          */
    263         p3.highline = l2;
    264
    265         if (withTicks) {
    266             /**
    267              * Ticks give a rough indication about the slider's current value.
    268              * @memberOf Slider.prototype
    269              * @name ticks
    270              * @type JXG.Ticks
    271              */
    272             p3.ticks = ti;
    273         }
    274
    275         // override the point's remove method to ensure the removal of all elements
    276         p3.remove = function () {
    277             if (withText) {
    278                 board.removeObject(t);
    279             }
    280
    281             board.removeObject(l2);
    282             board.removeObject(l1);
    283             board.removeObject(p2);
    284             board.removeObject(p1);
    285
    286
    287             Point.Point.prototype.remove.call(p3);
    288         };
    289
    290         p1.dump = false;
    291         p2.dump = false;
    292         l1.dump = false;
    293         l2.dump = false;
    294
    295         p3.elType = 'slider';
    296         p3.parents = parents;
    297         p3.subs = {
    298             point1: p1,
    299             point2: p2,
    300             baseLine: l1,
    301             highLine: l2
    302         };
    303
    304         if (withTicks) {
    305             ti.dump = false;
    306             p3.subs.ticks = ti;
    307         }
    308
    309         // Save the visibility attribute of the sub-elements
    310         for (el in p3.subs) {
    311             p3.subs[el].status = {
    312                 visible: p3.subs[el].visProp.visible
    313             };
    314         }
    315
    316         p3.hideElement = function () {
    317             var el;
    318             GeometryElement.prototype.hideElement.call(this);
    319
    320             for (el in this.subs) {
    321                 this.subs[el].status.visible = this.subs[el].visProp.visible;
    322                 this.subs[el].hideElement();
    323             }
    324         };
    325
    326         p3.showElement = function () {
    327             var el;
    328             GeometryElement.prototype.showElement.call(this);
    329
    330             for (el in this.subs) {
    331                 if (this.subs[el].status.visible) {
    332                     this.subs[el].showElement();
    333                 }
    334             }
    335         };
    336
    337         return p3;
    338     };
    339
    340     JXG.registerElement('slider', JXG.createSlider);
    341
    342     return {
    343         createSlider: JXG.createSlider
    344     };
    345 });
    346