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