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