1 /* 2 Copyright 2008-2018 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 = p3.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