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.

701 lines
17 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. /**
  2. * utility functions
  3. */
  4. var util = {};
  5. /**
  6. * Test whether given object is a number
  7. * @param {*} object
  8. * @return {Boolean} isNumber
  9. */
  10. util.isNumber = function isNumber(object) {
  11. return (object instanceof Number || typeof object == 'number');
  12. };
  13. /**
  14. * Test whether given object is a string
  15. * @param {*} object
  16. * @return {Boolean} isString
  17. */
  18. util.isString = function isString(object) {
  19. return (object instanceof String || typeof object == 'string');
  20. };
  21. /**
  22. * Test whether given object is a Date, or a String containing a Date
  23. * @param {Date | String} object
  24. * @return {Boolean} isDate
  25. */
  26. util.isDate = function isDate(object) {
  27. if (object instanceof Date) {
  28. return true;
  29. }
  30. else if (util.isString(object)) {
  31. // test whether this string contains a date
  32. var match = ASPDateRegex.exec(object);
  33. if (match) {
  34. return true;
  35. }
  36. else if (!isNaN(Date.parse(object))) {
  37. return true;
  38. }
  39. }
  40. return false;
  41. };
  42. /**
  43. * Test whether given object is an instance of google.visualization.DataTable
  44. * @param {*} object
  45. * @return {Boolean} isDataTable
  46. */
  47. util.isDataTable = function isDataTable(object) {
  48. return (typeof (google) !== 'undefined') &&
  49. (google.visualization) &&
  50. (google.visualization.DataTable) &&
  51. (object instanceof google.visualization.DataTable);
  52. };
  53. /**
  54. * Create a semi UUID
  55. * source: http://stackoverflow.com/a/105074/1262753
  56. * @return {String} uuid
  57. */
  58. util.randomUUID = function randomUUID () {
  59. var S4 = function () {
  60. return Math.floor(
  61. Math.random() * 0x10000 /* 65536 */
  62. ).toString(16);
  63. };
  64. return (
  65. S4() + S4() + '-' +
  66. S4() + '-' +
  67. S4() + '-' +
  68. S4() + '-' +
  69. S4() + S4() + S4()
  70. );
  71. };
  72. /**
  73. * Extend object a with the properties of object b or a series of objects
  74. * Only properties with defined values are copied
  75. * @param {Object} a
  76. * @param {... Object} b
  77. * @return {Object} a
  78. */
  79. util.extend = function (a, b) {
  80. for (var i = 1, len = arguments.length; i < len; i++) {
  81. var other = arguments[i];
  82. for (var prop in other) {
  83. if (other.hasOwnProperty(prop) && other[prop] !== undefined) {
  84. a[prop] = other[prop];
  85. }
  86. }
  87. }
  88. return a;
  89. };
  90. /**
  91. * Convert an object to another type
  92. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  93. * @param {String | undefined} type Name of the type. Available types:
  94. * 'Boolean', 'Number', 'String',
  95. * 'Date', 'Moment', ISODate', 'ASPDate'.
  96. * @return {*} object
  97. * @throws Error
  98. */
  99. util.convert = function convert(object, type) {
  100. var match;
  101. if (object === undefined) {
  102. return undefined;
  103. }
  104. if (object === null) {
  105. return null;
  106. }
  107. if (!type) {
  108. return object;
  109. }
  110. if (!(typeof type === 'string') && !(type instanceof String)) {
  111. throw new Error('Type must be a string');
  112. }
  113. //noinspection FallthroughInSwitchStatementJS
  114. switch (type) {
  115. case 'boolean':
  116. case 'Boolean':
  117. return Boolean(object);
  118. case 'number':
  119. case 'Number':
  120. return Number(object.valueOf());
  121. case 'string':
  122. case 'String':
  123. return String(object);
  124. case 'Date':
  125. if (util.isNumber(object)) {
  126. return new Date(object);
  127. }
  128. if (object instanceof Date) {
  129. return new Date(object.valueOf());
  130. }
  131. else if (moment.isMoment(object)) {
  132. return new Date(object.valueOf());
  133. }
  134. if (util.isString(object)) {
  135. match = ASPDateRegex.exec(object);
  136. if (match) {
  137. // object is an ASP date
  138. return new Date(Number(match[1])); // parse number
  139. }
  140. else {
  141. return moment(object).toDate(); // parse string
  142. }
  143. }
  144. else {
  145. throw new Error(
  146. 'Cannot convert object of type ' + util.getType(object) +
  147. ' to type Date');
  148. }
  149. case 'Moment':
  150. if (util.isNumber(object)) {
  151. return moment(object);
  152. }
  153. if (object instanceof Date) {
  154. return moment(object.valueOf());
  155. }
  156. else if (moment.isMoment(object)) {
  157. return moment(object);
  158. }
  159. if (util.isString(object)) {
  160. match = ASPDateRegex.exec(object);
  161. if (match) {
  162. // object is an ASP date
  163. return moment(Number(match[1])); // parse number
  164. }
  165. else {
  166. return moment(object); // parse string
  167. }
  168. }
  169. else {
  170. throw new Error(
  171. 'Cannot convert object of type ' + util.getType(object) +
  172. ' to type Date');
  173. }
  174. case 'ISODate':
  175. if (util.isNumber(object)) {
  176. return new Date(object);
  177. }
  178. else if (object instanceof Date) {
  179. return object.toISOString();
  180. }
  181. else if (moment.isMoment(object)) {
  182. return object.toDate().toISOString();
  183. }
  184. else if (util.isString(object)) {
  185. match = ASPDateRegex.exec(object);
  186. if (match) {
  187. // object is an ASP date
  188. return new Date(Number(match[1])).toISOString(); // parse number
  189. }
  190. else {
  191. return new Date(object).toISOString(); // parse string
  192. }
  193. }
  194. else {
  195. throw new Error(
  196. 'Cannot convert object of type ' + util.getType(object) +
  197. ' to type ISODate');
  198. }
  199. case 'ASPDate':
  200. if (util.isNumber(object)) {
  201. return '/Date(' + object + ')/';
  202. }
  203. else if (object instanceof Date) {
  204. return '/Date(' + object.valueOf() + ')/';
  205. }
  206. else if (util.isString(object)) {
  207. match = ASPDateRegex.exec(object);
  208. var value;
  209. if (match) {
  210. // object is an ASP date
  211. value = new Date(Number(match[1])).valueOf(); // parse number
  212. }
  213. else {
  214. value = new Date(object).valueOf(); // parse string
  215. }
  216. return '/Date(' + value + ')/';
  217. }
  218. else {
  219. throw new Error(
  220. 'Cannot convert object of type ' + util.getType(object) +
  221. ' to type ASPDate');
  222. }
  223. default:
  224. throw new Error('Cannot convert object of type ' + util.getType(object) +
  225. ' to type "' + type + '"');
  226. }
  227. };
  228. // parse ASP.Net Date pattern,
  229. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  230. // code from http://momentjs.com/
  231. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  232. /**
  233. * Get the type of an object, for example util.getType([]) returns 'Array'
  234. * @param {*} object
  235. * @return {String} type
  236. */
  237. util.getType = function getType(object) {
  238. var type = typeof object;
  239. if (type == 'object') {
  240. if (object == null) {
  241. return 'null';
  242. }
  243. if (object instanceof Boolean) {
  244. return 'Boolean';
  245. }
  246. if (object instanceof Number) {
  247. return 'Number';
  248. }
  249. if (object instanceof String) {
  250. return 'String';
  251. }
  252. if (object instanceof Array) {
  253. return 'Array';
  254. }
  255. if (object instanceof Date) {
  256. return 'Date';
  257. }
  258. return 'Object';
  259. }
  260. else if (type == 'number') {
  261. return 'Number';
  262. }
  263. else if (type == 'boolean') {
  264. return 'Boolean';
  265. }
  266. else if (type == 'string') {
  267. return 'String';
  268. }
  269. return type;
  270. };
  271. /**
  272. * Retrieve the absolute left value of a DOM element
  273. * @param {Element} elem A dom element, for example a div
  274. * @return {number} left The absolute left position of this element
  275. * in the browser page.
  276. */
  277. util.getAbsoluteLeft = function getAbsoluteLeft (elem) {
  278. var doc = document.documentElement;
  279. var body = document.body;
  280. var left = elem.offsetLeft;
  281. var e = elem.offsetParent;
  282. while (e != null && e != body && e != doc) {
  283. left += e.offsetLeft;
  284. left -= e.scrollLeft;
  285. e = e.offsetParent;
  286. }
  287. return left;
  288. };
  289. /**
  290. * Retrieve the absolute top value of a DOM element
  291. * @param {Element} elem A dom element, for example a div
  292. * @return {number} top The absolute top position of this element
  293. * in the browser page.
  294. */
  295. util.getAbsoluteTop = function getAbsoluteTop (elem) {
  296. var doc = document.documentElement;
  297. var body = document.body;
  298. var top = elem.offsetTop;
  299. var e = elem.offsetParent;
  300. while (e != null && e != body && e != doc) {
  301. top += e.offsetTop;
  302. top -= e.scrollTop;
  303. e = e.offsetParent;
  304. }
  305. return top;
  306. };
  307. /**
  308. * Get the absolute, vertical mouse position from an event.
  309. * @param {Event} event
  310. * @return {Number} pageY
  311. */
  312. util.getPageY = function getPageY (event) {
  313. if ('pageY' in event) {
  314. return event.pageY;
  315. }
  316. else {
  317. var clientY;
  318. if (('targetTouches' in event) && event.targetTouches.length) {
  319. clientY = event.targetTouches[0].clientY;
  320. }
  321. else {
  322. clientY = event.clientY;
  323. }
  324. var doc = document.documentElement;
  325. var body = document.body;
  326. return clientY +
  327. ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
  328. ( doc && doc.clientTop || body && body.clientTop || 0 );
  329. }
  330. };
  331. /**
  332. * Get the absolute, horizontal mouse position from an event.
  333. * @param {Event} event
  334. * @return {Number} pageX
  335. */
  336. util.getPageX = function getPageX (event) {
  337. if ('pageY' in event) {
  338. return event.pageX;
  339. }
  340. else {
  341. var clientX;
  342. if (('targetTouches' in event) && event.targetTouches.length) {
  343. clientX = event.targetTouches[0].clientX;
  344. }
  345. else {
  346. clientX = event.clientX;
  347. }
  348. var doc = document.documentElement;
  349. var body = document.body;
  350. return clientX +
  351. ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
  352. ( doc && doc.clientLeft || body && body.clientLeft || 0 );
  353. }
  354. };
  355. /**
  356. * add a className to the given elements style
  357. * @param {Element} elem
  358. * @param {String} className
  359. */
  360. util.addClassName = function addClassName(elem, className) {
  361. var classes = elem.className.split(' ');
  362. if (classes.indexOf(className) == -1) {
  363. classes.push(className); // add the class to the array
  364. elem.className = classes.join(' ');
  365. }
  366. };
  367. /**
  368. * add a className to the given elements style
  369. * @param {Element} elem
  370. * @param {String} className
  371. */
  372. util.removeClassName = function removeClassname(elem, className) {
  373. var classes = elem.className.split(' ');
  374. var index = classes.indexOf(className);
  375. if (index != -1) {
  376. classes.splice(index, 1); // remove the class from the array
  377. elem.className = classes.join(' ');
  378. }
  379. };
  380. /**
  381. * For each method for both arrays and objects.
  382. * In case of an array, the built-in Array.forEach() is applied.
  383. * In case of an Object, the method loops over all properties of the object.
  384. * @param {Object | Array} object An Object or Array
  385. * @param {function} callback Callback method, called for each item in
  386. * the object or array with three parameters:
  387. * callback(value, index, object)
  388. */
  389. util.forEach = function forEach (object, callback) {
  390. var i,
  391. len;
  392. if (object instanceof Array) {
  393. // array
  394. for (i = 0, len = object.length; i < len; i++) {
  395. callback(object[i], i, object);
  396. }
  397. }
  398. else {
  399. // object
  400. for (i in object) {
  401. if (object.hasOwnProperty(i)) {
  402. callback(object[i], i, object);
  403. }
  404. }
  405. }
  406. };
  407. /**
  408. * Update a property in an object
  409. * @param {Object} object
  410. * @param {String} key
  411. * @param {*} value
  412. * @return {Boolean} changed
  413. */
  414. util.updateProperty = function updateProp (object, key, value) {
  415. if (object[key] !== value) {
  416. object[key] = value;
  417. return true;
  418. }
  419. else {
  420. return false;
  421. }
  422. };
  423. /**
  424. * Add and event listener. Works for all browsers
  425. * @param {Element} element An html element
  426. * @param {string} action The action, for example "click",
  427. * without the prefix "on"
  428. * @param {function} listener The callback function to be executed
  429. * @param {boolean} [useCapture]
  430. */
  431. util.addEventListener = function addEventListener(element, action, listener, useCapture) {
  432. if (element.addEventListener) {
  433. if (useCapture === undefined)
  434. useCapture = false;
  435. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  436. action = "DOMMouseScroll"; // For Firefox
  437. }
  438. element.addEventListener(action, listener, useCapture);
  439. } else {
  440. element.attachEvent("on" + action, listener); // IE browsers
  441. }
  442. };
  443. /**
  444. * Remove an event listener from an element
  445. * @param {Element} element An html dom element
  446. * @param {string} action The name of the event, for example "mousedown"
  447. * @param {function} listener The listener function
  448. * @param {boolean} [useCapture]
  449. */
  450. util.removeEventListener = function removeEventListener(element, action, listener, useCapture) {
  451. if (element.removeEventListener) {
  452. // non-IE browsers
  453. if (useCapture === undefined)
  454. useCapture = false;
  455. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  456. action = "DOMMouseScroll"; // For Firefox
  457. }
  458. element.removeEventListener(action, listener, useCapture);
  459. } else {
  460. // IE browsers
  461. element.detachEvent("on" + action, listener);
  462. }
  463. };
  464. /**
  465. * Get HTML element which is the target of the event
  466. * @param {Event} event
  467. * @return {Element} target element
  468. */
  469. util.getTarget = function getTarget(event) {
  470. // code from http://www.quirksmode.org/js/events_properties.html
  471. if (!event) {
  472. event = window.event;
  473. }
  474. var target;
  475. if (event.target) {
  476. target = event.target;
  477. }
  478. else if (event.srcElement) {
  479. target = event.srcElement;
  480. }
  481. if (target.nodeType != undefined && target.nodeType == 3) {
  482. // defeat Safari bug
  483. target = target.parentNode;
  484. }
  485. return target;
  486. };
  487. /**
  488. * Stop event propagation
  489. */
  490. util.stopPropagation = function stopPropagation(event) {
  491. if (!event)
  492. event = window.event;
  493. if (event.stopPropagation) {
  494. event.stopPropagation(); // non-IE browsers
  495. }
  496. else {
  497. event.cancelBubble = true; // IE browsers
  498. }
  499. };
  500. /**
  501. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  502. * @param {Element} element
  503. * @param {Event} event
  504. */
  505. util.fakeGesture = function fakeGesture (element, event) {
  506. var eventType = null;
  507. // for hammer.js 1.0.5
  508. return Hammer.event.collectEventData(this, eventType, event);
  509. // for hammer.js 1.0.6
  510. //var touches = Hammer.event.getTouchList(event, eventType);
  511. //return Hammer.event.collectEventData(this, eventType, touches, event);
  512. };
  513. /**
  514. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  515. */
  516. util.preventDefault = function preventDefault (event) {
  517. if (!event)
  518. event = window.event;
  519. if (event.preventDefault) {
  520. event.preventDefault(); // non-IE browsers
  521. }
  522. else {
  523. event.returnValue = false; // IE browsers
  524. }
  525. };
  526. util.option = {};
  527. /**
  528. * Convert a value into a boolean
  529. * @param {Boolean | function | undefined} value
  530. * @param {Boolean} [defaultValue]
  531. * @returns {Boolean} bool
  532. */
  533. util.option.asBoolean = function (value, defaultValue) {
  534. if (typeof value == 'function') {
  535. value = value();
  536. }
  537. if (value != null) {
  538. return (value != false);
  539. }
  540. return defaultValue || null;
  541. };
  542. /**
  543. * Convert a value into a number
  544. * @param {Boolean | function | undefined} value
  545. * @param {Number} [defaultValue]
  546. * @returns {Number} number
  547. */
  548. util.option.asNumber = function (value, defaultValue) {
  549. if (typeof value == 'function') {
  550. value = value();
  551. }
  552. if (value != null) {
  553. return Number(value) || defaultValue || null;
  554. }
  555. return defaultValue || null;
  556. };
  557. /**
  558. * Convert a value into a string
  559. * @param {String | function | undefined} value
  560. * @param {String} [defaultValue]
  561. * @returns {String} str
  562. */
  563. util.option.asString = function (value, defaultValue) {
  564. if (typeof value == 'function') {
  565. value = value();
  566. }
  567. if (value != null) {
  568. return String(value);
  569. }
  570. return defaultValue || null;
  571. };
  572. /**
  573. * Convert a size or location into a string with pixels or a percentage
  574. * @param {String | Number | function | undefined} value
  575. * @param {String} [defaultValue]
  576. * @returns {String} size
  577. */
  578. util.option.asSize = function (value, defaultValue) {
  579. if (typeof value == 'function') {
  580. value = value();
  581. }
  582. if (util.isString(value)) {
  583. return value;
  584. }
  585. else if (util.isNumber(value)) {
  586. return value + 'px';
  587. }
  588. else {
  589. return defaultValue || null;
  590. }
  591. };
  592. /**
  593. * Convert a value into a DOM element
  594. * @param {HTMLElement | function | undefined} value
  595. * @param {HTMLElement} [defaultValue]
  596. * @returns {HTMLElement | null} dom
  597. */
  598. util.option.asElement = function (value, defaultValue) {
  599. if (typeof value == 'function') {
  600. value = value();
  601. }
  602. return value || defaultValue || null;
  603. };
  604. /**
  605. * Compare two numbers and return the lowest, non-negative number.
  606. *
  607. * @param {number} number1
  608. * @param {number} number2
  609. * @returns {number} | number1 or number2, the lowest positive number. If both negative, return -1
  610. * @private
  611. */
  612. util._getLowestPositiveNumber = function(number1,number2) {
  613. if (number1 >= 0) {
  614. if (number2 >= 0) {
  615. return (number1 < number2) ? number1 : number2;
  616. }
  617. else {
  618. return number1;
  619. }
  620. }
  621. else {
  622. if (number2 >= 0) {
  623. return number2;
  624. }
  625. else {
  626. return -1;
  627. }
  628. }
  629. }