      1 /*
      2     Copyright 2008-2017
      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 class="jxgbox" 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], [0, 10, 50]], {snapWidth: 1, ticks: { drawLabels: true }});
     89      * </pre><div class="jxgbox" 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, ticks: { drawLabels: true }});
     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             g,
    101             withText, withTicks, snapWidth, attr, precision, el;
    102
    103         attr = Type.copyAttributes(attributes, board.options, 'slider');
    104         withTicks = attr.withticks;
    105         withText = attr.withlabel;
    106         snapWidth = attr.snapwidth;
    107         precision = attr.precision;
    108
    109         // start point
    110         attr = Type.copyAttributes(attributes, board.options, 'slider', 'point1');
    111         p1 = board.create('point', parents[0],  attr);
    112
    113         // end point
    114         attr = Type.copyAttributes(attributes, board.options, 'slider', 'point2');
    115         p2 = board.create('point', parents[1],  attr);
    116         //g = board.create('group', [p1, p2]);
    117
    118         // slide line
    119         attr = Type.copyAttributes(attributes, board.options, 'slider', 'baseline');
    120         l1 = board.create('segment', [p1, p2], attr);
    121
    122         // this is required for a correct projection of the glider onto the segment below
    123         l1.updateStdform();
    124
    125         pos0 = p1.coords.usrCoords.slice(1);
    126         pos1 = p2.coords.usrCoords.slice(1);
    127         smin = parents[2][0];
    128         start = parents[2][1];
    129         smax = parents[2][2];
    130         sdiff = smax - smin;
    131
    132         startx = pos0[0] + (pos1[0] - pos0[0]) * (start - smin) / (smax - smin);
    133         starty = pos0[1] + (pos1[1] - pos0[1]) * (start - smin) / (smax - smin);
    134
    135         // glider point
    136         attr = Type.copyAttributes(attributes, board.options, 'slider');
    137         // overwrite this in any case; the sliders label is a special text element, not the gliders label.
    138         // this will be set back to true after the text was created (and only if withlabel was true initially).
    139         attr.withLabel = false;
    140         // gliders set snapwidth=-1 by default (i.e. deactivate them)
    141         p3 = board.create('glider', [startx, starty, l1], attr);
    142         p3.setAttribute({snapwidth: snapWidth});
    143
    144         // segment from start point to glider point
    145         attr = Type.copyAttributes(attributes, board.options, 'slider', 'highline');
    146         l2 = board.create('segment', [p1, p3],  attr);
    147
    148         /**
    149          * Returns the current slider value.
    150          * @memberOf Slider.prototype
    151          * @name Value
    152          * @returns {Number}
    153          */
    154         p3.Value = function () {
    155             var sdiff = this._smax - this._smin,
    156                 ev_sw = Type.evaluate(this.visProp.snapwidth);
    157
    158             return ev_sw === -1 ?
    159                         this.position * sdiff + this._smin :
    160                         Math.round((this.position * sdiff + this._smin) / ev_sw) * ev_sw;
    161         };
    162
    163         p3.methodMap = Type.deepCopy(p3.methodMap, {
    164             Value: 'Value',
    165             setValue: 'setValue',
    166             smax: '_smax',
    167             smin: '_smin',
    168             setMax: 'setMax',
    169             setMin: 'setMin'
    170         });
    171
    172         /**
    173          * End value of the slider range.
    174          * @memberOf Slider.prototype
    175          * @name _smax
    176          * @type Number
    177          */
    178         p3._smax = smax;
    179
    180         /**
    181          * Start value of the slider range.
    182          * @memberOf Slider.prototype
    183          * @name _smin
    184          * @type Number
    185          */
    186         p3._smin = smin;
    187
    188         /**
    189          * Sets the maximum value of the slider.
    190          * @memberOf Slider.prototype
    191          * @name setMax
    192          * @param {Number} val New maximum value
    193          * @returns {Object} this object
    194          */
    195         p3.setMax = function(val) {
    196             this._smax = val;
    197             return this;
    198         };
    199
    200         /**
    201          * Sets the value of the slider. This call must be followed
    202          * by a board update call.
    203          * @memberOf Slider.prototype
    204          * @name setValue
    205          * @param {Number} val New value
    206          * @returns {Object} this object
    207          */
    208         p3.setValue = function(val) {
    209             var sdiff = this._smax - this._smin;
    210
    211             if (Math.abs(sdiff) > Mat.eps) {
    212                 this.position = (val - this._smin) / sdiff;
    213             } else {
    214                 this.position = 0.0; //this._smin;
    215             }
    216             this.position = Math.max(0.0, Math.min(1.0, this.position));
    217             return this;
    218         };
    219
    220         /**
    221          * Sets the minimum value of the slider.
    222          * @memberOf Slider.prototype
    223          * @name setMin
    224          * @param {Number} val New minimum value
    225          * @returns {Object} this object
    226          */
    227         p3.setMin = function(val) {
    228             this._smin = val;
    229             return this;
    230         };
    231
    232         if (withText) {
    233             attr = Type.copyAttributes(attributes, board.options, 'slider', 'label');
    234             t = board.create('text', [
    235                 function () {
    236                     return (p2.X() - p1.X()) * 0.05 + p2.X();
    237                 },
    238                 function () {
    239                     return (p2.Y() - p1.Y()) * 0.05 + p2.Y();
    240                 },
    241                 function () {
    242                     var n,
    243                         sl = Type.evaluate(p3.visProp.suffixlabel),
    244                         ul = Type.evaluate(p3.visProp.unitlabel),
    245                         pl = Type.evaluate(p3.visProp.postlabel);
    246
    247                     if (sl !== null) {
    248                         n = sl;
    249                     } else if (p3.name && p3.name !== '') {
    250                         n = p3.name + ' = ';
    251                     } else {
    252                         n = '';
    253                     }
    254
    255                     n += Type.toFixed(p3.Value(), precision);
    256
    257                     if (ul !== null) {
    258                         n += ul;
    259                     }
    260                     if (pl !== null) {
    261                         n += pl;
    262                     }
    263
    264                     return n;
    265                 }
    266             ], attr);
    267
    268             /**
    269              * The text element to the right of the slider, indicating its current value.
    270              * @memberOf Slider.prototype
    271              * @name label
    272              * @type JXG.Text
    273              */
    274             p3.label = t;
    275
    276             // reset the withlabel attribute
    277             p3.visProp.withlabel = true;
    278             p3.hasLabel = true;
    279         }
    280
    281         /**
    282          * Start point of the base line.
    283          * @memberOf Slider.prototype
    284          * @name point1
    285          * @type JXG.Point
    286          */
    287         p3.point1 = p1;
    288
    289         /**
    290          * End point of the base line.
    291          * @memberOf Slider.prototype
    292          * @name point2
    293          * @type JXG.Point
    294          */
    295         p3.point2 = p2;
    296
    297         /**
    298          * The baseline the glider is bound to.
    299          * @memberOf Slider.prototype
    300          * @name baseline
    301          * @type JXG.Line
    302          */
    303         p3.baseline = l1;
    304
    305         /**
    306          * A line on top of the baseline, indicating the slider's progress.
    307          * @memberOf Slider.prototype
    308          * @name highline
    309          * @type JXG.Line
    310          */
    311         p3.highline = l2;
    312
    313         if (withTicks) {
    314             // Function to generate correct label texts
    315
    316             attr = Type.copyAttributes(attributes, board.options, 'slider', 'ticks');
    317             if (!Type.exists(attr.generatelabeltext)) {
    318                 attr.generateLabelText = function(tick, zero, value) {
    319                     var labelText,
    320                         dFull = p3.point1.Dist(p3.point2),
    321                         smin = p3._smin, smax = p3._smax,
    322                         val = this.getDistanceFromZero(zero, tick) * (smax - smin) / dFull  + smin;
    323
    324                         if (dFull < Mat.eps || Math.abs(val) < Mat.eps) { // Point is zero
    325                             labelText = '0';
    326                         } else {
    327                             labelText = this.formatLabelText(val);
    328                         }
    329                         return labelText;
    330                 };
    331             }
    332             ticks  = 2;
    333             ti = board.create('ticks', [
    334                 p3.baseline,
    335                 p3.point1.Dist(p1) / ticks,
    336
    337                 function (tick) {
    338                     var dFull = p3.point1.Dist(p3.point2),
    339                         d = p1p3.point1.coords.distance(Const.COORDS_BY_USER, tick);
    340
    341                     if (dFull < Mat.eps) {
    342                         return 0;
    343                     }
    344
    345                     return d / dFull * sdiff + smin;
    346                 }
    347             ], attr);
    348
    349             /**
    350              * Ticks give a rough indication about the slider's current value.
    351              * @memberOf Slider.prototype
    352              * @name ticks
    353              * @type JXG.Ticks
    354              */
    355             p3.ticks = ti;
    356         }
    357
    358         // override the point's remove method to ensure the removal of all elements
    359         p3.remove = function () {
    360             if (withText) {
    361                 board.removeObject(t);
    362             }
    363
    364             board.removeObject(l2);
    365             board.removeObject(l1);
    366             board.removeObject(p2);
    367             board.removeObject(p1);
    368
    369
    370             Point.Point.prototype.remove.call(p3);
    371         };
    372
    373         p1.dump = false;
    374         p2.dump = false;
    375         l1.dump = false;
    376         l2.dump = false;
    377
    378         p3.elType = 'slider';
    379         p3.parents = parents;
    380         p3.subs = {
    381             point1: p1,
    382             point2: p2,
    383             baseLine: l1,
    384             highLine: l2
    385         };
    386         p3.inherits.push(p1, p2, l1, l2);
    387
    388         if (withTicks) {
    389             ti.dump = false;
    390             p3.subs.ticks = ti;
    391             p3.inherits.push(ti);
    392         }
    393
    394         // Save the visibility attribute of the sub-elements
    395         // for (el in p3.subs) {
    396         //     p3.subs[el].status = {
    397         //         visible: p3.subs[el].visProp.visible
    398         //     };
    399         // }
    400
    401         // p3.hideElement = function () {
    402         //     var el;
    403         //     GeometryElement.prototype.hideElement.call(this);
    404         //
    405         //     for (el in this.subs) {
    406         //         // this.subs[el].status.visible = this.subs[el].visProp.visible;
    407         //         this.subs[el].hideElement();
    408         //     }
    409         // };
    410
    411 //         p3.showElement = function () {
    412 //             var el;
    413 //             GeometryElement.prototype.showElement.call(this);
    414 //
    415 //             for (el in this.subs) {
    416 // //                if (this.subs[el].status.visible) {
    417 //                 this.subs[el].showElement();
    418 // //                }
    419 //             }
    420 //         };
    421
    422         return p3;
    423     };
    424
    425     JXG.registerElement('slider', JXG.createSlider);
    426
    427     return {
    428         createSlider: JXG.createSlider
    429     };
    430 });
    431