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.

464 lines
14 KiB

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