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. * 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. //
  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.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 hiddenDates
  311. * @param range
  312. * @param time
  313. * @returns {{duration: number, time: *, offset: number}}
  314. */
  315. exports.correctTimeForHidden = function(hiddenDates, range, time) {
  316. time = moment(time).toDate().valueOf();
  317. time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
  318. return time;
  319. };
  320. exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
  321. var timeOffset = 0;
  322. time = moment(time).toDate().valueOf();
  323. for (var i = 0; i < hiddenDates.length; i++) {
  324. var startDate = hiddenDates[i].start;
  325. var endDate = hiddenDates[i].end;
  326. // if time after the cutout, and the
  327. if (startDate >= range.start && endDate < range.end) {
  328. if (time >= endDate) {
  329. timeOffset += (endDate - startDate);
  330. }
  331. }
  332. }
  333. return timeOffset;
  334. }
  335. /**
  336. * sum the duration from start to finish, including the hidden duration,
  337. * until the required amount has been reached, return the accumulated hidden duration
  338. * @param hiddenDates
  339. * @param range
  340. * @param time
  341. * @returns {{duration: number, time: *, offset: number}}
  342. */
  343. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  344. var hiddenDuration = 0;
  345. var duration = 0;
  346. var previousPoint = range.start;
  347. //exports.printDates(hiddenDates)
  348. for (var i = 0; i < hiddenDates.length; i++) {
  349. var startDate = hiddenDates[i].start;
  350. var endDate = hiddenDates[i].end;
  351. // if time after the cutout, and the
  352. if (startDate >= range.start && endDate < range.end) {
  353. duration += startDate - previousPoint;
  354. previousPoint = endDate;
  355. if (duration >= requiredDuration) {
  356. break;
  357. }
  358. else {
  359. hiddenDuration += endDate - startDate;
  360. }
  361. }
  362. }
  363. return hiddenDuration;
  364. };
  365. /**
  366. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  367. * @param hiddenDates
  368. * @param time
  369. * @param direction
  370. * @param correctionEnabled
  371. * @returns {*}
  372. */
  373. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  374. var isHidden = exports.isHidden(time, hiddenDates);
  375. if (isHidden.hidden == true) {
  376. if (direction < 0) {
  377. if (correctionEnabled == true) {
  378. return isHidden.startDate - (isHidden.endDate - time) - 1;
  379. }
  380. else {
  381. return isHidden.startDate - 1;
  382. }
  383. }
  384. else {
  385. if (correctionEnabled == true) {
  386. return isHidden.endDate + (time - isHidden.startDate) + 1;
  387. }
  388. else {
  389. return isHidden.endDate + 1;
  390. }
  391. }
  392. }
  393. else {
  394. return time;
  395. }
  396. }
  397. /**
  398. * Check if a time is hidden
  399. *
  400. * @param time
  401. * @param hiddenDates
  402. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  403. */
  404. exports.isHidden = function(time, hiddenDates) {
  405. for (var i = 0; i < hiddenDates.length; i++) {
  406. var startDate = hiddenDates[i].start;
  407. var endDate = hiddenDates[i].end;
  408. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  409. return {hidden: true, startDate: startDate, endDate: endDate};
  410. break;
  411. }
  412. }
  413. return {hidden: false, startDate: startDate, endDate: endDate};
  414. }