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.

461 lines
14 KiB

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