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.

443 lines
13 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. var specificHiddenDates = hiddenDates.specific;
  12. if (specificHiddenDates) {
  13. if (Array.isArray(specificHiddenDates) == true) {
  14. for (var i = 0; i < specificHiddenDates.length; i++) {
  15. var dateItem = {};
  16. dateItem.start = moment(specificHiddenDates[i].start).toDate().valueOf();
  17. dateItem.end = moment(specificHiddenDates[i].end).toDate().valueOf();
  18. body.hiddenDates.push(dateItem);
  19. }
  20. body.hiddenDates.sort(function (a, b) {
  21. return a.start - b.start;
  22. }); // sort by start time
  23. }
  24. else {
  25. body.hiddenDates = [{
  26. start: moment(specificHiddenDates.start).toDate().valueOf(),
  27. end: moment(specificHiddenDates.end).toDate().valueOf()
  28. }
  29. ];
  30. }
  31. }
  32. // allowing multiple input formats
  33. var periodicHiddenDates = hiddenDates.periodic;
  34. if (periodicHiddenDates) {
  35. if (periodicHiddenDates.times) {
  36. if (Array.isArray(periodicHiddenDates.times) != true) {
  37. periodicHiddenDates.times = [periodicHiddenDates.times];
  38. }
  39. }
  40. if (periodicHiddenDates.days) {
  41. if (Array.isArray(periodicHiddenDates.days) != true) {
  42. periodicHiddenDates.days = [periodicHiddenDates.days];
  43. }
  44. }
  45. }
  46. };
  47. /**
  48. * create new entrees for the periodic hidden dates
  49. * @param body
  50. * @param hiddenDates
  51. */
  52. exports.updateHiddenDates = function (body, hiddenDates) {
  53. if (hiddenDates && hiddenDates.periodic && body.domProps.centerContainer.width !== undefined) {
  54. body.hiddenDates = [];
  55. exports.convertHiddenOptions(body, hiddenDates);
  56. var start = moment(body.range.start);
  57. var end = moment(body.range.end);
  58. var totalRange = (body.range.end - body.range.start);
  59. var pixelTime = totalRange/body.domProps.centerContainer.width;
  60. if (hiddenDates.periodic.days) {
  61. var nextStartDay = moment(body.range.start);
  62. var nextEndDay = moment(body.range.start);
  63. for (var i = 0; i < hiddenDates.periodic.days.length; i++) {
  64. var startDay = hiddenDates.periodic.days[i].start;
  65. var endDay = hiddenDates.periodic.days[i].end;
  66. nextStartDay.isoWeekday(startDay);
  67. nextEndDay.isoWeekday(endDay);
  68. if (start < nextStartDay) {
  69. nextStartDay.isoWeekday(startDay - 7);
  70. }
  71. if (start < nextEndDay) {
  72. nextEndDay.isoWeekday(endDay - 7);
  73. }
  74. nextStartDay.milliseconds(0);
  75. nextStartDay.seconds(0);
  76. nextStartDay.minutes(0);
  77. nextStartDay.hours(0);
  78. nextEndDay.milliseconds(0);
  79. nextEndDay.seconds(0);
  80. nextEndDay.minutes(0);
  81. nextEndDay.hours(0);
  82. if (nextEndDay < nextStartDay) {
  83. nextEndDay.isoWeekday(endDay + 7);
  84. }
  85. var duration = nextEndDay - nextStartDay;
  86. if (duration < 4 * pixelTime) {
  87. break;
  88. }
  89. else {
  90. while (nextStartDay < end) {
  91. body.hiddenDates.push({start: nextStartDay.valueOf(), end: nextEndDay.valueOf()});
  92. nextStartDay.isoWeekday(startDay + 7);
  93. nextEndDay.isoWeekday(endDay + 7);
  94. }
  95. body.hiddenDates.push({start: nextStartDay.valueOf(), end: nextEndDay.valueOf()});
  96. }
  97. }
  98. }
  99. if (hiddenDates.periodic.times) {
  100. var nextStartDay = moment(body.range.start);
  101. var nextEndDay = moment(body.range.start);
  102. end = end.valueOf();
  103. for (var i = 0; i < hiddenDates.periodic.times.length; i++) {
  104. var startTime = hiddenDates.periodic.times[i].start.split(":");
  105. var endTime = hiddenDates.periodic.times[i].end.split(":");
  106. nextStartDay.milliseconds(0);
  107. nextStartDay.seconds(startTime[2]);
  108. nextStartDay.minutes(startTime[1]);
  109. nextStartDay.hours(startTime[0]);
  110. nextEndDay.milliseconds(0);
  111. nextEndDay.seconds(endTime[2]);
  112. nextEndDay.minutes(endTime[1]);
  113. nextEndDay.hours(endTime[0]);
  114. nextStartDay = nextStartDay.valueOf();
  115. nextEndDay = nextEndDay.valueOf();
  116. if (endTime[0] < startTime[0]) {
  117. nextEndDay += 3600000*24;
  118. }
  119. var duration = nextEndDay - nextStartDay;
  120. if (duration < 4 * pixelTime) {
  121. break;
  122. }
  123. else {
  124. nextStartDay -= 7*3600000*24;
  125. nextEndDay -= 7*3600000*24;
  126. while (nextStartDay < (end + 7*3600000*24)) {
  127. body.hiddenDates.push({start: nextStartDay.valueOf(), end: nextEndDay.valueOf()});
  128. nextStartDay += 3600000*24;
  129. nextEndDay += 3600000*24;
  130. }
  131. }
  132. }
  133. }
  134. // remove duplicates, merge where possible
  135. exports.removeDuplicates(body);
  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.getHiddenDuration(Core.body.hiddenDates, Core.range);
  251. time = exports.getHiddenTimeBefore(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.getHiddenDuration(body.hiddenDates, range);
  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 hiddenTimes
  275. * @param range
  276. * @returns {number}
  277. */
  278. exports.getHiddenDuration = function(hiddenTimes, range) {
  279. var duration = 0;
  280. for (var i = 0; i < hiddenTimes.length; i++) {
  281. var startDate = hiddenTimes[i].start;
  282. var endDate = hiddenTimes[i].end;
  283. // if time after the cutout, and the
  284. if (startDate >= range.start && endDate < range.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.getHiddenTimeBefore = function(hiddenDates, range, time) {
  298. var timeOffset = 0;
  299. time = moment(time).toDate().valueOf();
  300. for (var i = 0; i < hiddenDates.length; i++) {
  301. var startDate = hiddenDates[i].start;
  302. var endDate = hiddenDates[i].end;
  303. // if time after the cutout, and the
  304. if (startDate >= range.start && endDate < range.end) {
  305. if (time >= endDate) {
  306. timeOffset += (endDate - startDate);
  307. }
  308. }
  309. }
  310. time -= timeOffset;
  311. return time;
  312. };
  313. /**
  314. * Support function
  315. * @param hiddenDates
  316. * @param range
  317. * @param time
  318. * @returns {{duration: number, time: *, offset: number}}
  319. */
  320. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  321. var hiddenDuration = 0;
  322. var duration = 0;
  323. var previousPoint = range.start;
  324. //exports.printDates(hiddenDates)
  325. for (var i = 0; i < hiddenDates.length; i++) {
  326. var startDate = hiddenDates[i].start;
  327. var endDate = hiddenDates[i].end;
  328. // if time after the cutout, and the
  329. if (startDate >= range.start && endDate < range.end) {
  330. duration += startDate - previousPoint;
  331. previousPoint = endDate;
  332. if (duration >= requiredDuration) {
  333. break;
  334. }
  335. else {
  336. hiddenDuration += endDate - startDate;
  337. }
  338. }
  339. }
  340. return hiddenDuration;
  341. };
  342. /**
  343. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  344. * @param hiddenTimes
  345. * @param time
  346. * @param direction
  347. * @param correctionEnabled
  348. * @returns {*}
  349. */
  350. exports.snapAwayFromHidden = function(hiddenTimes, time, direction, correctionEnabled) {
  351. var isHidden = exports.isHidden(time, hiddenTimes);
  352. if (isHidden.hidden == true) {
  353. if (direction < 0) {
  354. if (correctionEnabled == true) {
  355. return isHidden.startDate - (isHidden.endDate - time) - 1;
  356. }
  357. else {
  358. return isHidden.startDate - 1;
  359. }
  360. }
  361. else {
  362. if (correctionEnabled == true) {
  363. return isHidden.endDate + (time - isHidden.startDate) + 1;
  364. }
  365. else {
  366. return isHidden.endDate + 1;
  367. }
  368. }
  369. }
  370. else {
  371. return time;
  372. }
  373. }
  374. /**
  375. * Check if a time is hidden
  376. *
  377. * @param time
  378. * @param hiddenTimes
  379. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  380. */
  381. exports.isHidden = function(time, hiddenTimes) {
  382. var isHidden = false;
  383. for (var i = 0; i < hiddenTimes.length; i++) {
  384. var startDate = hiddenTimes[i].start;
  385. var endDate = hiddenTimes[i].end;
  386. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  387. isHidden = true;
  388. break;
  389. }
  390. }
  391. return {hidden: isHidden, startDate: startDate, endDate: endDate};
  392. }