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.

410 lines
12 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 res = exports.correctTimeForDuration(Core.body.hiddenDates, Core.range, time);
  251. var duration = res.duration;
  252. time = res.time;
  253. var conversion = Core.range.conversion(width, duration);
  254. return (time.valueOf() - conversion.offset) * conversion.scale;
  255. };
  256. /**
  257. * Replaces the core toTime methods
  258. * @param body
  259. * @param range
  260. * @param x
  261. * @param width
  262. * @returns {Date}
  263. */
  264. exports.toTime = function(body, range, x, width) {
  265. var duration = exports.getHiddenDuration(body.hiddenDates, range);
  266. var conversion = range.conversion(width, duration);
  267. return new Date(x / conversion.scale + conversion.offset);
  268. };
  269. /**
  270. * Support function
  271. *
  272. * @param hiddenTimes
  273. * @param range
  274. * @returns {number}
  275. */
  276. exports.getHiddenDuration = function(hiddenTimes, range) {
  277. var duration = 0;
  278. for (var i = 0; i < hiddenTimes.length; i++) {
  279. var startDate = hiddenTimes[i].start;
  280. var endDate = hiddenTimes[i].end;
  281. // if time after the cutout, and the
  282. if (startDate >= range.start && endDate < range.end) {
  283. duration += endDate - startDate;
  284. }
  285. }
  286. return duration;
  287. };
  288. /**
  289. * Support function
  290. * @param hiddenTimes
  291. * @param range
  292. * @param time
  293. * @returns {{duration: number, time: *, offset: number}}
  294. */
  295. exports.correctTimeForDuration = function(hiddenTimes, range, time) {
  296. var duration = 0;
  297. var timeOffset = 0;
  298. time = moment(time).toDate().valueOf();
  299. for (var i = 0; i < hiddenTimes.length; i++) {
  300. var startDate = hiddenTimes[i].start;
  301. var endDate = hiddenTimes[i].end;
  302. // if time after the cutout, and the
  303. if (startDate >= range.start && endDate < range.end) {
  304. duration += (endDate - startDate);
  305. if (time >= endDate) {
  306. timeOffset += (endDate - startDate);
  307. }
  308. }
  309. }
  310. time -= timeOffset;
  311. return {duration: duration, time:time, offset: timeOffset};
  312. };
  313. /**
  314. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  315. * @param hiddenTimes
  316. * @param time
  317. * @param direction
  318. * @param correctionEnabled
  319. * @returns {*}
  320. */
  321. exports.snapAwayFromHidden = function(hiddenTimes, time, direction, correctionEnabled) {
  322. var isHidden = exports.isHidden(time, hiddenTimes);
  323. if (isHidden.hidden == true) {
  324. if (direction < 0) {
  325. if (correctionEnabled == true) {
  326. return isHidden.startDate - (isHidden.endDate - time) - 1;
  327. }
  328. else {
  329. return isHidden.startDate - 1;
  330. }
  331. }
  332. else {
  333. if (correctionEnabled == true) {
  334. return isHidden.endDate + (time - isHidden.startDate) + 1;
  335. }
  336. else {
  337. return isHidden.endDate + 1;
  338. }
  339. }
  340. }
  341. else {
  342. return time;
  343. }
  344. }
  345. /**
  346. * Check if a time is hidden
  347. *
  348. * @param time
  349. * @param hiddenTimes
  350. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  351. */
  352. exports.isHidden = function(time, hiddenTimes) {
  353. var isHidden = false;
  354. for (var i = 0; i < hiddenTimes.length; i++) {
  355. var startDate = hiddenTimes[i].start;
  356. var endDate = hiddenTimes[i].end;
  357. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  358. isHidden = true;
  359. break;
  360. }
  361. }
  362. return {hidden: isHidden, startDate: startDate, endDate: endDate};
  363. }