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.

506 lines
16 KiB

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