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.

463 lines
14 KiB

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