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.

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