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.

472 lines
15 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 | 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. }
  262. else {
  263. var hidden = exports.isHidden(time, Core.body.hiddenDates);
  264. if (hidden.hidden == true) {
  265. time = hidden.startDate;
  266. }
  267. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  268. time = exports.correctTimeForHidden(Core.options.moment, Core.body.hiddenDates, Core.range, time);
  269. var conversion = Core.range.conversion(width, duration);
  270. return (time.valueOf() - conversion.offset) * conversion.scale;
  271. }
  272. };
  273. /**
  274. * Replaces the core toTime methods
  275. * @param body
  276. * @param range
  277. * @param x
  278. * @param width
  279. * @returns {Date}
  280. */
  281. exports.toTime = function(Core, x, width) {
  282. if (Core.body.hiddenDates.length == 0) {
  283. var conversion = Core.range.conversion(width);
  284. return new Date(x / conversion.scale + conversion.offset);
  285. }
  286. else {
  287. var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  288. var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
  289. var partialDuration = totalDuration * x / width;
  290. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
  291. var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
  292. return newTime;
  293. }
  294. };
  295. /**
  296. * Support function
  297. *
  298. * @param hiddenDates
  299. * @param range
  300. * @returns {number}
  301. */
  302. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  303. var duration = 0;
  304. for (var i = 0; i < hiddenDates.length; i++) {
  305. var startDate = hiddenDates[i].start;
  306. var endDate = hiddenDates[i].end;
  307. // if time after the cutout, and the
  308. if (startDate >= start && endDate < end) {
  309. duration += endDate - startDate;
  310. }
  311. }
  312. return duration;
  313. };
  314. /**
  315. * Support function
  316. * @param moment
  317. * @param hiddenDates
  318. * @param range
  319. * @param time
  320. * @returns {{duration: number, time: *, offset: number}}
  321. */
  322. exports.correctTimeForHidden = function(moment, hiddenDates, range, time) {
  323. time = moment(time).toDate().valueOf();
  324. time -= exports.getHiddenDurationBefore(moment, hiddenDates,range,time);
  325. return time;
  326. };
  327. exports.getHiddenDurationBefore = function(moment, hiddenDates, range, time) {
  328. var timeOffset = 0;
  329. time = moment(time).toDate().valueOf();
  330. for (var i = 0; i < hiddenDates.length; i++) {
  331. var startDate = hiddenDates[i].start;
  332. var endDate = hiddenDates[i].end;
  333. // if time after the cutout, and the
  334. if (startDate >= range.start && endDate < range.end) {
  335. if (time >= endDate) {
  336. timeOffset += (endDate - startDate);
  337. }
  338. }
  339. }
  340. return timeOffset;
  341. }
  342. /**
  343. * sum the duration from start to finish, including the hidden duration,
  344. * until the required amount has been reached, return the accumulated hidden duration
  345. * @param hiddenDates
  346. * @param range
  347. * @param time
  348. * @returns {{duration: number, time: *, offset: number}}
  349. */
  350. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  351. var hiddenDuration = 0;
  352. var duration = 0;
  353. var previousPoint = range.start;
  354. //exports.printDates(hiddenDates)
  355. for (var i = 0; i < hiddenDates.length; i++) {
  356. var startDate = hiddenDates[i].start;
  357. var endDate = hiddenDates[i].end;
  358. // if time after the cutout, and the
  359. if (startDate >= range.start && endDate < range.end) {
  360. duration += startDate - previousPoint;
  361. previousPoint = endDate;
  362. if (duration >= requiredDuration) {
  363. break;
  364. }
  365. else {
  366. hiddenDuration += endDate - startDate;
  367. }
  368. }
  369. }
  370. return hiddenDuration;
  371. };
  372. /**
  373. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  374. * @param hiddenDates
  375. * @param time
  376. * @param direction
  377. * @param correctionEnabled
  378. * @returns {*}
  379. */
  380. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  381. var isHidden = exports.isHidden(time, hiddenDates);
  382. if (isHidden.hidden == true) {
  383. if (direction < 0) {
  384. if (correctionEnabled == true) {
  385. return isHidden.startDate - (isHidden.endDate - time) - 1;
  386. }
  387. else {
  388. return isHidden.startDate - 1;
  389. }
  390. }
  391. else {
  392. if (correctionEnabled == true) {
  393. return isHidden.endDate + (time - isHidden.startDate) + 1;
  394. }
  395. else {
  396. return isHidden.endDate + 1;
  397. }
  398. }
  399. }
  400. else {
  401. return time;
  402. }
  403. }
  404. /**
  405. * Check if a time is hidden
  406. *
  407. * @param time
  408. * @param hiddenDates
  409. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  410. */
  411. exports.isHidden = function(time, hiddenDates) {
  412. for (var i = 0; i < hiddenDates.length; i++) {
  413. var startDate = hiddenDates[i].start;
  414. var endDate = hiddenDates[i].end;
  415. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  416. return {hidden: true, startDate: startDate, endDate: endDate};
  417. break;
  418. }
  419. }
  420. return {hidden: false, startDate: startDate, endDate: endDate};
  421. }