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.

512 lines
16 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. /**
  2. * used in Core to convert the options into a volatile variable
  3. *
  4. * @param {function} moment
  5. * @param {Object} body
  6. * @param {Array | Object} hiddenDates
  7. * @returns {Number}
  8. */
  9. exports.convertHiddenOptions = function(moment, body, hiddenDates) {
  10. if (hiddenDates && !Array.isArray(hiddenDates)) {
  11. return exports.convertHiddenOptions(moment, body, [hiddenDates])
  12. }
  13. body.hiddenDates = [];
  14. if (hiddenDates) {
  15. if (Array.isArray(hiddenDates) == true) {
  16. for (var i = 0; i < hiddenDates.length; i++) {
  17. if (hiddenDates[i].repeat === undefined) {
  18. var dateItem = {};
  19. dateItem.start = moment(hiddenDates[i].start).toDate().valueOf();
  20. dateItem.end = moment(hiddenDates[i].end).toDate().valueOf();
  21. body.hiddenDates.push(dateItem);
  22. }
  23. }
  24. body.hiddenDates.sort(function (a, b) {
  25. return a.start - b.start;
  26. }); // sort by start time
  27. }
  28. }
  29. };
  30. /**
  31. * create new entrees for the repeating hidden dates
  32. *
  33. * @param {function} moment
  34. * @param {Object} body
  35. * @param {Array | Object} hiddenDates
  36. * @returns {null}
  37. */
  38. exports.updateHiddenDates = function (moment, body, hiddenDates) {
  39. if (hiddenDates && !Array.isArray(hiddenDates)) {
  40. return exports.updateHiddenDates(moment, body, [hiddenDates])
  41. }
  42. if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
  43. exports.convertHiddenOptions(moment, body, hiddenDates);
  44. var start = moment(body.range.start);
  45. var end = moment(body.range.end);
  46. var totalRange = (body.range.end - body.range.start);
  47. var pixelTime = totalRange / body.domProps.centerContainer.width;
  48. for (var i = 0; i < hiddenDates.length; i++) {
  49. if (hiddenDates[i].repeat !== undefined) {
  50. var startDate = moment(hiddenDates[i].start);
  51. var endDate = moment(hiddenDates[i].end);
  52. if (startDate._d == "Invalid Date") {
  53. throw new Error("Supplied start date is not valid: " + hiddenDates[i].start);
  54. }
  55. if (endDate._d == "Invalid Date") {
  56. throw new Error("Supplied end date is not valid: " + hiddenDates[i].end);
  57. }
  58. var duration = endDate - startDate;
  59. if (duration >= 4 * pixelTime) {
  60. var offset = 0;
  61. var runUntil = end.clone();
  62. switch (hiddenDates[i].repeat) {
  63. case "daily": // case of time
  64. if (startDate.day() != endDate.day()) {
  65. offset = 1;
  66. }
  67. startDate.dayOfYear(start.dayOfYear());
  68. startDate.year(start.year());
  69. startDate.subtract(7,'days');
  70. endDate.dayOfYear(start.dayOfYear());
  71. endDate.year(start.year());
  72. endDate.subtract(7 - offset,'days');
  73. runUntil.add(1, 'weeks');
  74. break;
  75. case "weekly":
  76. var dayOffset = endDate.diff(startDate,'days');
  77. var day = startDate.day();
  78. // set the start date to the range.start
  79. startDate.date(start.date());
  80. startDate.month(start.month());
  81. startDate.year(start.year());
  82. endDate = startDate.clone();
  83. // force
  84. startDate.day(day);
  85. endDate.day(day);
  86. endDate.add(dayOffset,'days');
  87. startDate.subtract(1,'weeks');
  88. endDate.subtract(1,'weeks');
  89. runUntil.add(1, 'weeks');
  90. break;
  91. case "monthly":
  92. if (startDate.month() != endDate.month()) {
  93. offset = 1;
  94. }
  95. startDate.month(start.month());
  96. startDate.year(start.year());
  97. startDate.subtract(1,'months');
  98. endDate.month(start.month());
  99. endDate.year(start.year());
  100. endDate.subtract(1,'months');
  101. endDate.add(offset,'months');
  102. runUntil.add(1, 'months');
  103. break;
  104. case "yearly":
  105. if (startDate.year() != endDate.year()) {
  106. offset = 1;
  107. }
  108. startDate.year(start.year());
  109. startDate.subtract(1,'years');
  110. endDate.year(start.year());
  111. endDate.subtract(1,'years');
  112. endDate.add(offset,'years');
  113. runUntil.add(1, 'years');
  114. break;
  115. default:
  116. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  117. return;
  118. }
  119. while (startDate < runUntil) {
  120. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  121. switch (hiddenDates[i].repeat) {
  122. case "daily":
  123. startDate.add(1, 'days');
  124. endDate.add(1, 'days');
  125. break;
  126. case "weekly":
  127. startDate.add(1, 'weeks');
  128. endDate.add(1, 'weeks');
  129. break;
  130. case "monthly":
  131. startDate.add(1, 'months');
  132. endDate.add(1, 'months');
  133. break;
  134. case "yearly":
  135. startDate.add(1, 'y');
  136. endDate.add(1, 'y');
  137. break;
  138. default:
  139. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  140. return;
  141. }
  142. }
  143. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  144. }
  145. }
  146. }
  147. // remove duplicates, merge where possible
  148. exports.removeDuplicates(body);
  149. // ensure the new positions are not on hidden dates
  150. var startHidden = exports.isHidden(body.range.start, body.hiddenDates);
  151. var endHidden = exports.isHidden(body.range.end,body.hiddenDates);
  152. var rangeStart = body.range.start;
  153. var rangeEnd = body.range.end;
  154. if (startHidden.hidden == true) {rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1;}
  155. if (endHidden.hidden == true) {rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1;}
  156. if (startHidden.hidden == true || endHidden.hidden == true) {
  157. body.range._applyRange(rangeStart, rangeEnd);
  158. }
  159. }
  160. };
  161. /**
  162. * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up.
  163. * Scales with N^2
  164. *
  165. * @param {Object} body
  166. */
  167. exports.removeDuplicates = function(body) {
  168. var hiddenDates = body.hiddenDates;
  169. var safeDates = [];
  170. for (var i = 0; i < hiddenDates.length; i++) {
  171. for (var j = 0; j < hiddenDates.length; j++) {
  172. if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) {
  173. // j inside i
  174. if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  175. hiddenDates[j].remove = true;
  176. }
  177. // j start inside i
  178. else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) {
  179. hiddenDates[i].end = hiddenDates[j].end;
  180. hiddenDates[j].remove = true;
  181. }
  182. // j end inside i
  183. else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  184. hiddenDates[i].start = hiddenDates[j].start;
  185. hiddenDates[j].remove = true;
  186. }
  187. }
  188. }
  189. }
  190. for (i = 0; i < hiddenDates.length; i++) {
  191. if (hiddenDates[i].remove !== true) {
  192. safeDates.push(hiddenDates[i]);
  193. }
  194. }
  195. body.hiddenDates = safeDates;
  196. body.hiddenDates.sort(function (a, b) {
  197. return a.start - b.start;
  198. }); // sort by start time
  199. };
  200. exports.printDates = function(dates) {
  201. for (var i =0; i < dates.length; i++) {
  202. console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
  203. }
  204. };
  205. /**
  206. * Used in TimeStep to avoid the hidden times.
  207. * @param {function} moment
  208. * @param {TimeStep} timeStep
  209. * @param {Date} previousTime
  210. */
  211. exports.stepOverHiddenDates = function(moment, timeStep, previousTime) {
  212. var stepInHidden = false;
  213. var currentValue = timeStep.current.valueOf();
  214. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  215. var startDate = timeStep.hiddenDates[i].start;
  216. var endDate = timeStep.hiddenDates[i].end;
  217. if (currentValue >= startDate && currentValue < endDate) {
  218. stepInHidden = true;
  219. break;
  220. }
  221. }
  222. if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
  223. var prevValue = moment(previousTime);
  224. var newValue = moment(endDate);
  225. //check if the next step should be major
  226. if (prevValue.year() != newValue.year()) {timeStep.switchedYear = true;}
  227. else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
  228. else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
  229. timeStep.current = newValue;
  230. }
  231. };
  232. ///**
  233. // * Used in TimeStep to avoid the hidden times.
  234. // * @param timeStep
  235. // * @param previousTime
  236. // */
  237. //exports.checkFirstStep = function(timeStep) {
  238. // var stepInHidden = false;
  239. // var currentValue = timeStep.current.valueOf();
  240. // for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  241. // var startDate = timeStep.hiddenDates[i].start;
  242. // var endDate = timeStep.hiddenDates[i].end;
  243. // if (currentValue >= startDate && currentValue < endDate) {
  244. // stepInHidden = true;
  245. // break;
  246. // }
  247. // }
  248. //
  249. // if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
  250. // var newValue = moment(endDate);
  251. // timeStep.current = newValue.toDate();
  252. // }
  253. //};
  254. /**
  255. * replaces the Core toScreen methods
  256. *
  257. * @param {vis.Core} Core
  258. * @param {Date} time
  259. * @param {Number} width
  260. * @returns {Number}
  261. */
  262. exports.toScreen = function (Core, time, width) {
  263. var conversion;
  264. if (Core.body.hiddenDates.length == 0) {
  265. conversion = Core.range.conversion(width);
  266. return (time.valueOf() - conversion.offset) * conversion.scale;
  267. } else {
  268. var hidden = exports.isHidden(time, Core.body.hiddenDates);
  269. if (hidden.hidden == true) {
  270. time = hidden.startDate;
  271. }
  272. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  273. if (time < Core.range.start) {
  274. conversion = Core.range.conversion(width, duration);
  275. var hiddenBeforeStart = exports.getHiddenDurationBeforeStart(Core.body.hiddenDates, time, conversion.offset);
  276. time = Core.options.moment(time).toDate().valueOf();
  277. time = time + hiddenBeforeStart;
  278. return -(conversion.offset - time.valueOf()) * conversion.scale;
  279. } else if (time > Core.range.end) {
  280. var rangeAfterEnd = {start: Core.range.start, end: time};
  281. time = exports.correctTimeForHidden(Core.options.moment, Core.body.hiddenDates, rangeAfterEnd, time);
  282. conversion = Core.range.conversion(width, duration);
  283. return (time.valueOf() - conversion.offset) * conversion.scale;
  284. } else {
  285. time = exports.correctTimeForHidden(Core.options.moment, Core.body.hiddenDates, Core.range, time);
  286. conversion = Core.range.conversion(width, duration);
  287. return (time.valueOf() - conversion.offset) * conversion.scale;
  288. }
  289. }
  290. };
  291. /**
  292. * Replaces the core toTime methods
  293. *
  294. * @param {vis.Core} Core
  295. * @param {number} x
  296. * @param {number} width
  297. * @returns {Date}
  298. */
  299. exports.toTime = function(Core, x, width) {
  300. if (Core.body.hiddenDates.length == 0) {
  301. var conversion = Core.range.conversion(width);
  302. return new Date(x / conversion.scale + conversion.offset);
  303. }
  304. else {
  305. var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  306. var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
  307. var partialDuration = totalDuration * x / width;
  308. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
  309. return new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
  310. }
  311. };
  312. /**
  313. * Support function
  314. *
  315. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  316. * @param {number} start
  317. * @param {number} end
  318. * @returns {number}
  319. */
  320. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  321. var duration = 0;
  322. for (var i = 0; i < hiddenDates.length; i++) {
  323. var startDate = hiddenDates[i].start;
  324. var endDate = hiddenDates[i].end;
  325. // if time after the cutout, and the
  326. if (startDate >= start && endDate < end) {
  327. duration += endDate - startDate;
  328. }
  329. }
  330. return duration;
  331. };
  332. /**
  333. * Support function
  334. *
  335. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  336. * @param {number} start
  337. * @param {number} end
  338. * @returns {number}
  339. */
  340. exports.getHiddenDurationBeforeStart = function (hiddenDates, start, end) {
  341. var duration = 0;
  342. for (var i = 0; i < hiddenDates.length; i++) {
  343. var startDate = hiddenDates[i].start;
  344. var endDate = hiddenDates[i].end;
  345. if (startDate >= start && endDate <= end) {
  346. duration += endDate - startDate;
  347. }
  348. }
  349. return duration;
  350. };
  351. /**
  352. * Support function
  353. * @param {function} moment
  354. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  355. * @param {{start: number, end: number}} range
  356. * @param {Date} time
  357. * @returns {number}
  358. */
  359. exports.correctTimeForHidden = function(moment, hiddenDates, range, time) {
  360. time = moment(time).toDate().valueOf();
  361. time -= exports.getHiddenDurationBefore(moment, hiddenDates,range,time);
  362. return time;
  363. };
  364. exports.getHiddenDurationBefore = function(moment, hiddenDates, range, time) {
  365. var timeOffset = 0;
  366. time = moment(time).toDate().valueOf();
  367. for (var i = 0; i < hiddenDates.length; i++) {
  368. var startDate = hiddenDates[i].start;
  369. var endDate = hiddenDates[i].end;
  370. // if time after the cutout, and the
  371. if (startDate >= range.start && endDate < range.end) {
  372. if (time >= endDate) {
  373. timeOffset += (endDate - startDate);
  374. }
  375. }
  376. }
  377. return timeOffset;
  378. };
  379. /**
  380. * sum the duration from start to finish, including the hidden duration,
  381. * until the required amount has been reached, return the accumulated hidden duration
  382. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  383. * @param {{start: number, end: number}} range
  384. * @param {number} [requiredDuration=0]
  385. * @returns {number}
  386. */
  387. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  388. var hiddenDuration = 0;
  389. var duration = 0;
  390. var previousPoint = range.start;
  391. //exports.printDates(hiddenDates)
  392. for (var i = 0; i < hiddenDates.length; i++) {
  393. var startDate = hiddenDates[i].start;
  394. var endDate = hiddenDates[i].end;
  395. // if time after the cutout, and the
  396. if (startDate >= range.start && endDate < range.end) {
  397. duration += startDate - previousPoint;
  398. previousPoint = endDate;
  399. if (duration >= requiredDuration) {
  400. break;
  401. }
  402. else {
  403. hiddenDuration += endDate - startDate;
  404. }
  405. }
  406. }
  407. return hiddenDuration;
  408. };
  409. /**
  410. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  411. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  412. * @param {Date} time
  413. * @param {Number} direction
  414. * @param {Boolean} correctionEnabled
  415. * @returns {Date|Number}
  416. */
  417. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  418. var isHidden = exports.isHidden(time, hiddenDates);
  419. if (isHidden.hidden == true) {
  420. if (direction < 0) {
  421. if (correctionEnabled == true) {
  422. return isHidden.startDate - (isHidden.endDate - time) - 1;
  423. }
  424. else {
  425. return isHidden.startDate - 1;
  426. }
  427. }
  428. else {
  429. if (correctionEnabled == true) {
  430. return isHidden.endDate + (time - isHidden.startDate) + 1;
  431. }
  432. else {
  433. return isHidden.endDate + 1;
  434. }
  435. }
  436. }
  437. else {
  438. return time;
  439. }
  440. };
  441. /**
  442. * Check if a time is hidden
  443. *
  444. * @param {Date} time
  445. * @param {Array<{start: Window.start, end: *}>} hiddenDates
  446. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  447. */
  448. exports.isHidden = function(time, hiddenDates) {
  449. for (var i = 0; i < hiddenDates.length; i++) {
  450. var startDate = hiddenDates[i].start;
  451. var endDate = hiddenDates[i].end;
  452. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  453. return {hidden: true, startDate: startDate, endDate: endDate};
  454. }
  455. }
  456. return {hidden: false, startDate: startDate, endDate: endDate};
  457. };