vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

678 lines
22 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. var util = require('../util');
  2. var hammerUtil = require('../hammerUtil');
  3. var moment = require('../module/moment');
  4. var Component = require('./component/Component');
  5. var DateUtil = require('./DateUtil');
  6. /**
  7. * @constructor Range
  8. * A Range controls a numeric range with a start and end value.
  9. * The Range adjusts the range based on mouse events or programmatic changes,
  10. * and triggers events when the range is changing or has been changed.
  11. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  12. * @param {Object} [options] See description at Range.setOptions
  13. */
  14. function Range(body, options) {
  15. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  16. this.start = now.clone().add(-3, 'days').valueOf(); // Number
  17. this.end = now.clone().add(4, 'days').valueOf(); // Number
  18. this.body = body;
  19. this.deltaDifference = 0;
  20. this.scaleOffset = 0;
  21. this.startToFront = false;
  22. this.endToFront = true;
  23. // default options
  24. this.defaultOptions = {
  25. start: null,
  26. end: null,
  27. direction: 'horizontal', // 'horizontal' or 'vertical'
  28. moveable: true,
  29. zoomable: true,
  30. min: null,
  31. max: null,
  32. zoomMin: 10, // milliseconds
  33. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  34. };
  35. this.options = util.extend({}, this.defaultOptions);
  36. this.props = {
  37. touch: {}
  38. };
  39. this.animationTimer = null;
  40. // drag listeners for dragging
  41. this.body.emitter.on('panstart', this._onDragStart.bind(this));
  42. this.body.emitter.on('panmove', this._onDrag.bind(this));
  43. this.body.emitter.on('panend', this._onDragEnd.bind(this));
  44. // mouse wheel for zooming
  45. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  46. // pinch to zoom
  47. this.body.emitter.on('touch', this._onTouch.bind(this));
  48. this.body.emitter.on('pinch', this._onPinch.bind(this));
  49. this.setOptions(options);
  50. }
  51. Range.prototype = new Component();
  52. /**
  53. * Set options for the range controller
  54. * @param {Object} options Available options:
  55. * {Number | Date | String} start Start date for the range
  56. * {Number | Date | String} end End date for the range
  57. * {Number} min Minimum value for start
  58. * {Number} max Maximum value for end
  59. * {Number} zoomMin Set a minimum value for
  60. * (end - start).
  61. * {Number} zoomMax Set a maximum value for
  62. * (end - start).
  63. * {Boolean} moveable Enable moving of the range
  64. * by dragging. True by default
  65. * {Boolean} zoomable Enable zooming of the range
  66. * by pinching/scrolling. True by default
  67. */
  68. Range.prototype.setOptions = function (options) {
  69. if (options) {
  70. // copy the options that we know
  71. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates'];
  72. util.selectiveExtend(fields, this.options, options);
  73. if ('start' in options || 'end' in options) {
  74. // apply a new range. both start and end are optional
  75. this.setRange(options.start, options.end);
  76. }
  77. }
  78. };
  79. /**
  80. * Test whether direction has a valid value
  81. * @param {String} direction 'horizontal' or 'vertical'
  82. */
  83. function validateDirection (direction) {
  84. if (direction != 'horizontal' && direction != 'vertical') {
  85. throw new TypeError('Unknown direction "' + direction + '". ' +
  86. 'Choose "horizontal" or "vertical".');
  87. }
  88. }
  89. /**
  90. * Set a new start and end range
  91. * @param {Date | Number | String} [start]
  92. * @param {Date | Number | String} [end]
  93. * @param {boolean | {duration: number, easingFunction: string}} [animation=false]
  94. * If true (default), the range is animated
  95. * smoothly to the new window. An object can be
  96. * provided to specify duration and easing function.
  97. * Default duration is 500 ms, and default easing
  98. * function is 'easeInOutQuad'.
  99. * @param {Boolean} [byUser=false]
  100. *
  101. */
  102. Range.prototype.setRange = function(start, end, animation, byUser) {
  103. if (byUser !== true) {
  104. byUser = false;
  105. }
  106. var finalStart = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  107. var finalEnd = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  108. this._cancelAnimation();
  109. if (animation) { // true or an Object
  110. var me = this;
  111. var initStart = this.start;
  112. var initEnd = this.end;
  113. var duration = (typeof animation === 'object' && 'duration' in animation) ? animation.duration : 500;
  114. var easingName = (typeof animation === 'object' && 'easingFunction' in animation) ? animation.easingFunction : 'easeInOutQuad';
  115. var easingFunction = util.easingFunctions[easingName];
  116. if (!easingFunction) {
  117. throw new Error('Unknown easing function ' + JSON.stringify(easingName) + '. ' +
  118. 'Choose from: ' + Object.keys(util.easingFunctions).join(', '));
  119. }
  120. var initTime = new Date().valueOf();
  121. var anyChanged = false;
  122. var next = function () {
  123. if (!me.props.touch.dragging) {
  124. var now = new Date().valueOf();
  125. var time = now - initTime;
  126. var ease = easingFunction(time / duration);
  127. var done = time > duration;
  128. var s = (done || finalStart === null) ? finalStart : initStart + (finalStart - initStart) * ease;
  129. var e = (done || finalEnd === null) ? finalEnd : initEnd + (finalEnd - initEnd) * ease;
  130. changed = me._applyRange(s, e);
  131. DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
  132. anyChanged = anyChanged || changed;
  133. if (changed) {
  134. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  135. }
  136. if (done) {
  137. if (anyChanged) {
  138. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end), byUser:byUser});
  139. }
  140. }
  141. else {
  142. // animate with as high as possible frame rate, leave 20 ms in between
  143. // each to prevent the browser from blocking
  144. me.animationTimer = setTimeout(next, 20);
  145. }
  146. }
  147. };
  148. return next();
  149. }
  150. else {
  151. var changed = this._applyRange(finalStart, finalEnd);
  152. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  153. if (changed) {
  154. var params = {start: new Date(this.start), end: new Date(this.end), byUser:byUser};
  155. this.body.emitter.emit('rangechange', params);
  156. this.body.emitter.emit('rangechanged', params);
  157. }
  158. }
  159. };
  160. /**
  161. * Stop an animation
  162. * @private
  163. */
  164. Range.prototype._cancelAnimation = function () {
  165. if (this.animationTimer) {
  166. clearTimeout(this.animationTimer);
  167. this.animationTimer = null;
  168. }
  169. };
  170. /**
  171. * Set a new start and end range. This method is the same as setRange, but
  172. * does not trigger a range change and range changed event, and it returns
  173. * true when the range is changed
  174. * @param {Number} [start]
  175. * @param {Number} [end]
  176. * @return {Boolean} changed
  177. * @private
  178. */
  179. Range.prototype._applyRange = function(start, end) {
  180. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  181. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  182. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  183. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  184. diff;
  185. // check for valid number
  186. if (isNaN(newStart) || newStart === null) {
  187. throw new Error('Invalid start "' + start + '"');
  188. }
  189. if (isNaN(newEnd) || newEnd === null) {
  190. throw new Error('Invalid end "' + end + '"');
  191. }
  192. // prevent start < end
  193. if (newEnd < newStart) {
  194. newEnd = newStart;
  195. }
  196. // prevent start < min
  197. if (min !== null) {
  198. if (newStart < min) {
  199. diff = (min - newStart);
  200. newStart += diff;
  201. newEnd += diff;
  202. // prevent end > max
  203. if (max != null) {
  204. if (newEnd > max) {
  205. newEnd = max;
  206. }
  207. }
  208. }
  209. }
  210. // prevent end > max
  211. if (max !== null) {
  212. if (newEnd > max) {
  213. diff = (newEnd - max);
  214. newStart -= diff;
  215. newEnd -= diff;
  216. // prevent start < min
  217. if (min != null) {
  218. if (newStart < min) {
  219. newStart = min;
  220. }
  221. }
  222. }
  223. }
  224. // prevent (end-start) < zoomMin
  225. if (this.options.zoomMin !== null) {
  226. var zoomMin = parseFloat(this.options.zoomMin);
  227. if (zoomMin < 0) {
  228. zoomMin = 0;
  229. }
  230. if ((newEnd - newStart) < zoomMin) {
  231. if ((this.end - this.start) === zoomMin && newStart > this.start && newEnd < this.end) {
  232. // ignore this action, we are already zoomed to the minimum
  233. newStart = this.start;
  234. newEnd = this.end;
  235. }
  236. else {
  237. // zoom to the minimum
  238. diff = (zoomMin - (newEnd - newStart));
  239. newStart -= diff / 2;
  240. newEnd += diff / 2;
  241. }
  242. }
  243. }
  244. // prevent (end-start) > zoomMax
  245. if (this.options.zoomMax !== null) {
  246. var zoomMax = parseFloat(this.options.zoomMax);
  247. if (zoomMax < 0) {
  248. zoomMax = 0;
  249. }
  250. if ((newEnd - newStart) > zoomMax) {
  251. if ((this.end - this.start) === zoomMax && newStart < this.start && newEnd > this.end) {
  252. // ignore this action, we are already zoomed to the maximum
  253. newStart = this.start;
  254. newEnd = this.end;
  255. }
  256. else {
  257. // zoom to the maximum
  258. diff = ((newEnd - newStart) - zoomMax);
  259. newStart += diff / 2;
  260. newEnd -= diff / 2;
  261. }
  262. }
  263. }
  264. var changed = (this.start != newStart || this.end != newEnd);
  265. // if the new range does NOT overlap with the old range, emit checkRangedItems to avoid not showing ranged items (ranged meaning has end time, not necessarily of type Range)
  266. if (!((newStart >= this.start && newStart <= this.end) || (newEnd >= this.start && newEnd <= this.end)) &&
  267. !((this.start >= newStart && this.start <= newEnd) || (this.end >= newStart && this.end <= newEnd) )) {
  268. this.body.emitter.emit('checkRangedItems');
  269. }
  270. this.start = newStart;
  271. this.end = newEnd;
  272. return changed;
  273. };
  274. /**
  275. * Retrieve the current range.
  276. * @return {Object} An object with start and end properties
  277. */
  278. Range.prototype.getRange = function() {
  279. return {
  280. start: this.start,
  281. end: this.end
  282. };
  283. };
  284. /**
  285. * Calculate the conversion offset and scale for current range, based on
  286. * the provided width
  287. * @param {Number} width
  288. * @returns {{offset: number, scale: number}} conversion
  289. */
  290. Range.prototype.conversion = function (width, totalHidden) {
  291. return Range.conversion(this.start, this.end, width, totalHidden);
  292. };
  293. /**
  294. * Static method to calculate the conversion offset and scale for a range,
  295. * based on the provided start, end, and width
  296. * @param {Number} start
  297. * @param {Number} end
  298. * @param {Number} width
  299. * @returns {{offset: number, scale: number}} conversion
  300. */
  301. Range.conversion = function (start, end, width, totalHidden) {
  302. if (totalHidden === undefined) {
  303. totalHidden = 0;
  304. }
  305. if (width != 0 && (end - start != 0)) {
  306. return {
  307. offset: start,
  308. scale: width / (end - start - totalHidden)
  309. }
  310. }
  311. else {
  312. return {
  313. offset: 0,
  314. scale: 1
  315. };
  316. }
  317. };
  318. /**
  319. * Start dragging horizontally or vertically
  320. * @param {Event} event
  321. * @private
  322. */
  323. Range.prototype._onDragStart = function(event) {
  324. this.deltaDifference = 0;
  325. this.previousDelta = 0;
  326. // only allow dragging when configured as movable
  327. if (!this.options.moveable) return;
  328. // refuse to drag when we where pinching to prevent the timeline make a jump
  329. // when releasing the fingers in opposite order from the touch screen
  330. if (!this.props.touch.allowDragging) return;
  331. this.props.touch.start = this.start;
  332. this.props.touch.end = this.end;
  333. this.props.touch.dragging = true;
  334. if (this.body.dom.root) {
  335. this.body.dom.root.style.cursor = 'move';
  336. }
  337. };
  338. /**
  339. * Perform dragging operation
  340. * @param {Event} event
  341. * @private
  342. */
  343. Range.prototype._onDrag = function (event) {
  344. // only allow dragging when configured as movable
  345. if (!this.options.moveable) return;
  346. // TODO: this may be redundant in hammerjs2
  347. // refuse to drag when we where pinching to prevent the timeline make a jump
  348. // when releasing the fingers in opposite order from the touch screen
  349. if (!this.props.touch.allowDragging) return;
  350. var direction = this.options.direction;
  351. validateDirection(direction);
  352. var delta = (direction == 'horizontal') ? event.deltaX : event.deltaY;
  353. delta -= this.deltaDifference;
  354. var interval = (this.props.touch.end - this.props.touch.start);
  355. // normalize dragging speed if cutout is in between.
  356. var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  357. interval -= duration;
  358. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  359. var diffRange = -delta / width * interval;
  360. var newStart = this.props.touch.start + diffRange;
  361. var newEnd = this.props.touch.end + diffRange;
  362. // snapping times away from hidden zones
  363. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta-delta, true);
  364. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta-delta, true);
  365. if (safeStart != newStart || safeEnd != newEnd) {
  366. this.deltaDifference += delta;
  367. this.props.touch.start = safeStart;
  368. this.props.touch.end = safeEnd;
  369. this._onDrag(event);
  370. return;
  371. }
  372. this.previousDelta = delta;
  373. this._applyRange(newStart, newEnd);
  374. // fire a rangechange event
  375. this.body.emitter.emit('rangechange', {
  376. start: new Date(this.start),
  377. end: new Date(this.end),
  378. byUser: true
  379. });
  380. };
  381. /**
  382. * Stop dragging operation
  383. * @param {event} event
  384. * @private
  385. */
  386. Range.prototype._onDragEnd = function (event) {
  387. // only allow dragging when configured as movable
  388. if (!this.options.moveable) return;
  389. // TODO: this may be redundant in hammerjs2
  390. // refuse to drag when we where pinching to prevent the timeline make a jump
  391. // when releasing the fingers in opposite order from the touch screen
  392. if (!this.props.touch.allowDragging) return;
  393. this.props.touch.dragging = false;
  394. if (this.body.dom.root) {
  395. this.body.dom.root.style.cursor = 'auto';
  396. }
  397. // fire a rangechanged event
  398. this.body.emitter.emit('rangechanged', {
  399. start: new Date(this.start),
  400. end: new Date(this.end),
  401. byUser: true
  402. });
  403. };
  404. /**
  405. * Event handler for mouse wheel event, used to zoom
  406. * Code from http://adomas.org/javascript-mouse-wheel/
  407. * @param {Event} event
  408. * @private
  409. */
  410. Range.prototype._onMouseWheel = function(event) {
  411. // only allow zooming when configured as zoomable and moveable
  412. if (!(this.options.zoomable && this.options.moveable)) return;
  413. // retrieve delta
  414. var delta = 0;
  415. if (event.wheelDelta) { /* IE/Opera. */
  416. delta = event.wheelDelta / 120;
  417. } else if (event.detail) { /* Mozilla case. */
  418. // In Mozilla, sign of delta is different than in IE.
  419. // Also, delta is multiple of 3.
  420. delta = -event.detail / 3;
  421. }
  422. // If delta is nonzero, handle it.
  423. // Basically, delta is now positive if wheel was scrolled up,
  424. // and negative, if wheel was scrolled down.
  425. if (delta) {
  426. // perform the zoom action. Delta is normally 1 or -1
  427. // adjust a negative delta such that zooming in with delta 0.1
  428. // equals zooming out with a delta -0.1
  429. var scale;
  430. if (delta < 0) {
  431. scale = 1 - (delta / 5);
  432. }
  433. else {
  434. scale = 1 / (1 + (delta / 5)) ;
  435. }
  436. // calculate center, the date to zoom around
  437. var pointer = getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
  438. var pointerDate = this._pointerToDate(pointer);
  439. this.zoom(scale, pointerDate, delta);
  440. }
  441. // Prevent default actions caused by mouse wheel
  442. // (else the page and timeline both zoom and scroll)
  443. event.preventDefault();
  444. };
  445. /**
  446. * Start of a touch gesture
  447. * @private
  448. */
  449. Range.prototype._onTouch = function (event) {
  450. this.props.touch.start = this.start;
  451. this.props.touch.end = this.end;
  452. this.props.touch.allowDragging = true;
  453. this.props.touch.center = null;
  454. this.scaleOffset = 0;
  455. this.deltaDifference = 0;
  456. };
  457. /**
  458. * Handle pinch event
  459. * @param {Event} event
  460. * @private
  461. */
  462. Range.prototype._onPinch = function (event) {
  463. // only allow zooming when configured as zoomable and moveable
  464. if (!(this.options.zoomable && this.options.moveable)) return;
  465. this.props.touch.allowDragging = false;
  466. if (!this.props.touch.center) {
  467. this.props.touch.center = getPointer(event.center, this.body.dom.center);
  468. }
  469. var scale = 1 / (event.scale + this.scaleOffset);
  470. var centerDate = this._pointerToDate(this.props.touch.center);
  471. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  472. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, centerDate);
  473. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  474. // calculate new start and end
  475. var newStart = (centerDate - hiddenDurationBefore) + (this.props.touch.start - (centerDate - hiddenDurationBefore)) * scale;
  476. var newEnd = (centerDate + hiddenDurationAfter) + (this.props.touch.end - (centerDate + hiddenDurationAfter)) * scale;
  477. // snapping times away from hidden zones
  478. this.startToFront = 1 - scale <= 0; // used to do the right auto correction with periodic hidden times
  479. this.endToFront = scale - 1 <= 0; // used to do the right auto correction with periodic hidden times
  480. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true);
  481. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true);
  482. if (safeStart != newStart || safeEnd != newEnd) {
  483. this.props.touch.start = safeStart;
  484. this.props.touch.end = safeEnd;
  485. this.scaleOffset = 1 - event.scale;
  486. newStart = safeStart;
  487. newEnd = safeEnd;
  488. }
  489. this.setRange(newStart, newEnd, false, true);
  490. this.startToFront = false; // revert to default
  491. this.endToFront = true; // revert to default
  492. };
  493. /**
  494. * Helper function to calculate the center date for zooming
  495. * @param {{x: Number, y: Number}} pointer
  496. * @return {number} date
  497. * @private
  498. */
  499. Range.prototype._pointerToDate = function (pointer) {
  500. var conversion;
  501. var direction = this.options.direction;
  502. validateDirection(direction);
  503. if (direction == 'horizontal') {
  504. return this.body.util.toTime(pointer.x).valueOf();
  505. }
  506. else {
  507. var height = this.body.domProps.center.height;
  508. conversion = this.conversion(height);
  509. return pointer.y / conversion.scale + conversion.offset;
  510. }
  511. };
  512. /**
  513. * Get the pointer location relative to the location of the dom element
  514. * @param {{x: Number, y: Number}} touch
  515. * @param {Element} element HTML DOM element
  516. * @return {{x: Number, y: Number}} pointer
  517. * @private
  518. */
  519. function getPointer (touch, element) {
  520. return {
  521. x: touch.x - util.getAbsoluteLeft(element),
  522. y: touch.y - util.getAbsoluteTop(element)
  523. };
  524. }
  525. /**
  526. * Zoom the range the given scale in or out. Start and end date will
  527. * be adjusted, and the timeline will be redrawn. You can optionally give a
  528. * date around which to zoom.
  529. * For example, try scale = 0.9 or 1.1
  530. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  531. * values below 1 will zoom in.
  532. * @param {Number} [center] Value representing a date around which will
  533. * be zoomed.
  534. */
  535. Range.prototype.zoom = function(scale, center, delta) {
  536. // if centerDate is not provided, take it half between start Date and end Date
  537. if (center == null) {
  538. center = (this.start + this.end) / 2;
  539. }
  540. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  541. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  542. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  543. // calculate new start and end
  544. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  545. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  546. // snapping times away from hidden zones
  547. this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  548. this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  549. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true);
  550. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true);
  551. if (safeStart != newStart || safeEnd != newEnd) {
  552. newStart = safeStart;
  553. newEnd = safeEnd;
  554. }
  555. this.setRange(newStart, newEnd, false, true);
  556. this.startToFront = false; // revert to default
  557. this.endToFront = true; // revert to default
  558. };
  559. /**
  560. * Move the range with a given delta to the left or right. Start and end
  561. * value will be adjusted. For example, try delta = 0.1 or -0.1
  562. * @param {Number} delta Moving amount. Positive value will move right,
  563. * negative value will move left
  564. */
  565. Range.prototype.move = function(delta) {
  566. // zoom start Date and end Date relative to the centerDate
  567. var diff = (this.end - this.start);
  568. // apply new values
  569. var newStart = this.start + diff * delta;
  570. var newEnd = this.end + diff * delta;
  571. // TODO: reckon with min and max range
  572. this.start = newStart;
  573. this.end = newEnd;
  574. };
  575. /**
  576. * Move the range to a new center point
  577. * @param {Number} moveTo New center point of the range
  578. */
  579. Range.prototype.moveTo = function(moveTo) {
  580. var center = (this.start + this.end) / 2;
  581. var diff = center - moveTo;
  582. // calculate new start and end
  583. var newStart = this.start - diff;
  584. var newEnd = this.end - diff;
  585. this.setRange(newStart, newEnd);
  586. };
  587. module.exports = Range;