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.

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