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.

841 lines
24 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
  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
  74. * @param {Object} a
  75. * @param {Object} b
  76. * @return {Object} a
  77. */
  78. util.extend = function (a, b) {
  79. for (var prop in b) {
  80. if (b.hasOwnProperty(prop)) {
  81. a[prop] = b[prop];
  82. }
  83. }
  84. return a;
  85. };
  86. /**
  87. * Cast an object to another type
  88. * @param {Boolean | Number | String | Date | Null | undefined} object
  89. * @param {String | function | undefined} type Name of the type or a cast
  90. * function. Available types:
  91. * 'Boolean', 'Number', 'String',
  92. * 'Date', ISODate', 'ASPDate'.
  93. * @return {*} object
  94. * @throws Error
  95. */
  96. util.cast = function cast(object, type) {
  97. if (object === undefined) {
  98. return undefined;
  99. }
  100. if (object === null) {
  101. return null;
  102. }
  103. if (!type) {
  104. return object;
  105. }
  106. if (typeof type == 'function') {
  107. return type(object);
  108. }
  109. //noinspection FallthroughInSwitchStatementJS
  110. switch (type) {
  111. case 'boolean':
  112. case 'Boolean':
  113. return Boolean(object);
  114. case 'number':
  115. case 'Number':
  116. return Number(object);
  117. case 'string':
  118. case 'String':
  119. return String(object);
  120. case 'Date':
  121. if (util.isNumber(object)) {
  122. return new Date(object);
  123. }
  124. if (object instanceof Date) {
  125. return new Date(object.valueOf());
  126. }
  127. if (util.isString(object)) {
  128. // parse ASP.Net Date pattern,
  129. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  130. // code from http://momentjs.com/
  131. var match = ASPDateRegex.exec(object);
  132. if (match) {
  133. return new Date(Number(match[1]));
  134. }
  135. else {
  136. return moment(object).toDate(); // parse string
  137. }
  138. }
  139. else {
  140. throw new Error(
  141. 'Cannot cast object of type ' + util.getType(object) +
  142. ' to type Date');
  143. }
  144. case 'ISODate':
  145. if (object instanceof Date) {
  146. return object.toISOString();
  147. }
  148. else if (util.isNumber(object) || util.isString(object)) {
  149. return moment(object).toDate().toISOString();
  150. }
  151. else {
  152. throw new Error(
  153. 'Cannot cast object of type ' + util.getType(object) +
  154. ' to type ISODate');
  155. }
  156. case 'ASPDate':
  157. if (object instanceof Date) {
  158. return '/Date(' + object.valueOf() + ')/';
  159. }
  160. else if (util.isNumber(object) || util.isString(object)) {
  161. return '/Date(' + moment(object).valueOf() + ')/';
  162. }
  163. else {
  164. throw new Error(
  165. 'Cannot cast object of type ' + util.getType(object) +
  166. ' to type ASPDate');
  167. }
  168. default:
  169. throw new Error('Cannot cast object of type ' + util.getType(object) +
  170. ' to type "' + type + '"');
  171. }
  172. };
  173. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  174. /**
  175. * Get the type of an object, for example util.getType([]) returns 'Array'
  176. * @param {*} object
  177. * @return {String} type
  178. */
  179. util.getType = function getType(object) {
  180. var type = typeof object;
  181. if (type == 'object') {
  182. if (object == null) {
  183. return 'null';
  184. }
  185. if (object instanceof Boolean) {
  186. return 'Boolean';
  187. }
  188. if (object instanceof Number) {
  189. return 'Number';
  190. }
  191. if (object instanceof String) {
  192. return 'String';
  193. }
  194. if (object instanceof Array) {
  195. return 'Array';
  196. }
  197. if (object instanceof Date) {
  198. return 'Date';
  199. }
  200. return 'Object';
  201. }
  202. else if (type == 'number') {
  203. return 'Number';
  204. }
  205. else if (type == 'boolean') {
  206. return 'Boolean';
  207. }
  208. else if (type == 'string') {
  209. return 'String';
  210. }
  211. return type;
  212. };
  213. /**
  214. * Retrieve the absolute left value of a DOM element
  215. * @param {Element} elem A dom element, for example a div
  216. * @return {number} left The absolute left position of this element
  217. * in the browser page.
  218. */
  219. util.getAbsoluteLeft = function getAbsoluteLeft (elem) {
  220. var doc = document.documentElement;
  221. var body = document.body;
  222. var left = elem.offsetLeft;
  223. var e = elem.offsetParent;
  224. while (e != null && e != body && e != doc) {
  225. left += e.offsetLeft;
  226. left -= e.scrollLeft;
  227. e = e.offsetParent;
  228. }
  229. return left;
  230. };
  231. /**
  232. * Retrieve the absolute top value of a DOM element
  233. * @param {Element} elem A dom element, for example a div
  234. * @return {number} top The absolute top position of this element
  235. * in the browser page.
  236. */
  237. util.getAbsoluteTop = function getAbsoluteTop (elem) {
  238. var doc = document.documentElement;
  239. var body = document.body;
  240. var top = elem.offsetTop;
  241. var e = elem.offsetParent;
  242. while (e != null && e != body && e != doc) {
  243. top += e.offsetTop;
  244. top -= e.scrollTop;
  245. e = e.offsetParent;
  246. }
  247. return top;
  248. };
  249. /**
  250. * Get the absolute, vertical mouse position from an event.
  251. * @param {Event} event
  252. * @return {Number} pageY
  253. */
  254. util.getPageY = function getPageY (event) {
  255. if ('pageY' in event) {
  256. return event.pageY;
  257. }
  258. else {
  259. var clientY;
  260. if (('targetTouches' in event) && event.targetTouches.length) {
  261. clientY = event.targetTouches[0].clientY;
  262. }
  263. else {
  264. clientY = event.clientY;
  265. }
  266. var doc = document.documentElement;
  267. var body = document.body;
  268. return clientY +
  269. ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
  270. ( doc && doc.clientTop || body && body.clientTop || 0 );
  271. }
  272. };
  273. /**
  274. * Get the absolute, horizontal mouse position from an event.
  275. * @param {Event} event
  276. * @return {Number} pageX
  277. */
  278. util.getPageX = function getPageX (event) {
  279. if ('pageY' in event) {
  280. return event.pageX;
  281. }
  282. else {
  283. var clientX;
  284. if (('targetTouches' in event) && event.targetTouches.length) {
  285. clientX = event.targetTouches[0].clientX;
  286. }
  287. else {
  288. clientX = event.clientX;
  289. }
  290. var doc = document.documentElement;
  291. var body = document.body;
  292. return clientX +
  293. ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
  294. ( doc && doc.clientLeft || body && body.clientLeft || 0 );
  295. }
  296. };
  297. /**
  298. * add a className to the given elements style
  299. * @param {Element} elem
  300. * @param {String} className
  301. */
  302. util.addClassName = function addClassName(elem, className) {
  303. var classes = elem.className.split(' ');
  304. if (classes.indexOf(className) == -1) {
  305. classes.push(className); // add the class to the array
  306. elem.className = classes.join(' ');
  307. }
  308. };
  309. /**
  310. * add a className to the given elements style
  311. * @param {Element} elem
  312. * @param {String} className
  313. */
  314. util.removeClassName = function removeClassname(elem, className) {
  315. var classes = elem.className.split(' ');
  316. var index = classes.indexOf(className);
  317. if (index != -1) {
  318. classes.splice(index, 1); // remove the class from the array
  319. elem.className = classes.join(' ');
  320. }
  321. };
  322. /**
  323. * For each method for both arrays and objects.
  324. * In case of an array, the built-in Array.forEach() is applied.
  325. * In case of an Object, the method loops over all properties of the object.
  326. * @param {Object | Array} object An Object or Array
  327. * @param {function} callback Callback method, called for each item in
  328. * the object or array with three parameters:
  329. * callback(value, index, object)
  330. */
  331. util.forEach = function forEach (object, callback) {
  332. var i,
  333. len;
  334. if (object instanceof Array) {
  335. // array
  336. for (i = 0, len = object.length; i < len; i++) {
  337. callback(object[i], i, object);
  338. }
  339. }
  340. else {
  341. // object
  342. for (i in object) {
  343. if (object.hasOwnProperty(i)) {
  344. callback(object[i], i, object);
  345. }
  346. }
  347. }
  348. };
  349. /**
  350. * Update a property in an object
  351. * @param {Object} object
  352. * @param {String} key
  353. * @param {*} value
  354. * @return {Boolean} changed
  355. */
  356. util.updateProperty = function updateProp (object, key, value) {
  357. if (object[key] !== value) {
  358. object[key] = value;
  359. return true;
  360. }
  361. else {
  362. return false;
  363. }
  364. };
  365. /**
  366. * Add and event listener. Works for all browsers
  367. * @param {Element} element An html element
  368. * @param {string} action The action, for example "click",
  369. * without the prefix "on"
  370. * @param {function} listener The callback function to be executed
  371. * @param {boolean} [useCapture]
  372. */
  373. util.addEventListener = function addEventListener(element, action, listener, useCapture) {
  374. if (element.addEventListener) {
  375. if (useCapture === undefined)
  376. useCapture = false;
  377. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  378. action = "DOMMouseScroll"; // For Firefox
  379. }
  380. element.addEventListener(action, listener, useCapture);
  381. } else {
  382. element.attachEvent("on" + action, listener); // IE browsers
  383. }
  384. };
  385. /**
  386. * Remove an event listener from an element
  387. * @param {Element} element An html dom element
  388. * @param {string} action The name of the event, for example "mousedown"
  389. * @param {function} listener The listener function
  390. * @param {boolean} [useCapture]
  391. */
  392. util.removeEventListener = function removeEventListener(element, action, listener, useCapture) {
  393. if (element.removeEventListener) {
  394. // non-IE browsers
  395. if (useCapture === undefined)
  396. useCapture = false;
  397. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  398. action = "DOMMouseScroll"; // For Firefox
  399. }
  400. element.removeEventListener(action, listener, useCapture);
  401. } else {
  402. // IE browsers
  403. element.detachEvent("on" + action, listener);
  404. }
  405. };
  406. /**
  407. * Get HTML element which is the target of the event
  408. * @param {Event} event
  409. * @return {Element} target element
  410. */
  411. util.getTarget = function getTarget(event) {
  412. // code from http://www.quirksmode.org/js/events_properties.html
  413. if (!event) {
  414. event = window.event;
  415. }
  416. var target;
  417. if (event.target) {
  418. target = event.target;
  419. }
  420. else if (event.srcElement) {
  421. target = event.srcElement;
  422. }
  423. if (target.nodeType != undefined && target.nodeType == 3) {
  424. // defeat Safari bug
  425. target = target.parentNode;
  426. }
  427. return target;
  428. };
  429. /**
  430. * Stop event propagation
  431. */
  432. util.stopPropagation = function stopPropagation(event) {
  433. if (!event)
  434. event = window.event;
  435. if (event.stopPropagation) {
  436. event.stopPropagation(); // non-IE browsers
  437. }
  438. else {
  439. event.cancelBubble = true; // IE browsers
  440. }
  441. };
  442. /**
  443. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  444. */
  445. util.preventDefault = function preventDefault (event) {
  446. if (!event)
  447. event = window.event;
  448. if (event.preventDefault) {
  449. event.preventDefault(); // non-IE browsers
  450. }
  451. else {
  452. event.returnValue = false; // IE browsers
  453. }
  454. };
  455. util.option = {};
  456. /**
  457. * Cast a value as boolean
  458. * @param {Boolean | function | undefined} value
  459. * @param {Boolean} [defaultValue]
  460. * @returns {Boolean} bool
  461. */
  462. util.option.asBoolean = function (value, defaultValue) {
  463. if (typeof value == 'function') {
  464. value = value();
  465. }
  466. if (value != null) {
  467. return (value != false);
  468. }
  469. return defaultValue || null;
  470. };
  471. /**
  472. * Cast a value as number
  473. * @param {Boolean | function | undefined} value
  474. * @param {Number} [defaultValue]
  475. * @returns {Number} number
  476. */
  477. util.option.asNumber = function (value, defaultValue) {
  478. if (typeof value == 'function') {
  479. value = value();
  480. }
  481. if (value != null) {
  482. return Number(value);
  483. }
  484. return defaultValue || null;
  485. };
  486. /**
  487. * Cast a value as string
  488. * @param {String | function | undefined} value
  489. * @param {String} [defaultValue]
  490. * @returns {String} str
  491. */
  492. util.option.asString = function (value, defaultValue) {
  493. if (typeof value == 'function') {
  494. value = value();
  495. }
  496. if (value != null) {
  497. return String(value);
  498. }
  499. return defaultValue || null;
  500. };
  501. /**
  502. * Cast a size or location in pixels or a percentage
  503. * @param {String | Number | function | undefined} value
  504. * @param {String} [defaultValue]
  505. * @returns {String} size
  506. */
  507. util.option.asSize = function (value, defaultValue) {
  508. if (typeof value == 'function') {
  509. value = value();
  510. }
  511. if (util.isString(value)) {
  512. return value;
  513. }
  514. else if (util.isNumber(value)) {
  515. return value + 'px';
  516. }
  517. else {
  518. return defaultValue || null;
  519. }
  520. };
  521. /**
  522. * Cast a value as DOM element
  523. * @param {HTMLElement | function | undefined} value
  524. * @param {HTMLElement} [defaultValue]
  525. * @returns {HTMLElement | null} dom
  526. */
  527. util.option.asElement = function (value, defaultValue) {
  528. if (typeof value == 'function') {
  529. value = value();
  530. }
  531. return value || defaultValue || null;
  532. };
  533. /**
  534. * load css from text
  535. * @param {String} css Text containing css
  536. */
  537. util.loadCss = function (css) {
  538. if (typeof document === 'undefined') {
  539. return;
  540. }
  541. // get the script location, and built the css file name from the js file name
  542. // http://stackoverflow.com/a/2161748/1262753
  543. // var scripts = document.getElementsByTagName('script');
  544. // var jsFile = scripts[scripts.length-1].src.split('?')[0];
  545. // var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css';
  546. // inject css
  547. // http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
  548. var style = document.createElement('style');
  549. style.type = 'text/css';
  550. if (style.styleSheet){
  551. style.styleSheet.cssText = css;
  552. } else {
  553. style.appendChild(document.createTextNode(css));
  554. }
  555. document.getElementsByTagName('head')[0].appendChild(style);
  556. };
  557. // Internet Explorer 8 and older does not support Array.indexOf, so we define
  558. // it here in that case.
  559. // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
  560. if(!Array.prototype.indexOf) {
  561. Array.prototype.indexOf = function(obj){
  562. for(var i = 0; i < this.length; i++){
  563. if(this[i] == obj){
  564. return i;
  565. }
  566. }
  567. return -1;
  568. };
  569. try {
  570. console.log("Warning: Ancient browser detected. Please update your browser");
  571. }
  572. catch (err) {
  573. }
  574. }
  575. // Internet Explorer 8 and older does not support Array.forEach, so we define
  576. // it here in that case.
  577. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
  578. if (!Array.prototype.forEach) {
  579. Array.prototype.forEach = function(fn, scope) {
  580. for(var i = 0, len = this.length; i < len; ++i) {
  581. fn.call(scope || this, this[i], i, this);
  582. }
  583. }
  584. }
  585. // Internet Explorer 8 and older does not support Array.map, so we define it
  586. // here in that case.
  587. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
  588. // Production steps of ECMA-262, Edition 5, 15.4.4.19
  589. // Reference: http://es5.github.com/#x15.4.4.19
  590. if (!Array.prototype.map) {
  591. Array.prototype.map = function(callback, thisArg) {
  592. var T, A, k;
  593. if (this == null) {
  594. throw new TypeError(" this is null or not defined");
  595. }
  596. // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
  597. var O = Object(this);
  598. // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
  599. // 3. Let len be ToUint32(lenValue).
  600. var len = O.length >>> 0;
  601. // 4. If IsCallable(callback) is false, throw a TypeError exception.
  602. // See: http://es5.github.com/#x9.11
  603. if (typeof callback !== "function") {
  604. throw new TypeError(callback + " is not a function");
  605. }
  606. // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
  607. if (thisArg) {
  608. T = thisArg;
  609. }
  610. // 6. Let A be a new array created as if by the expression new Array(len) where Array is
  611. // the standard built-in constructor with that name and len is the value of len.
  612. A = new Array(len);
  613. // 7. Let k be 0
  614. k = 0;
  615. // 8. Repeat, while k < len
  616. while(k < len) {
  617. var kValue, mappedValue;
  618. // a. Let Pk be ToString(k).
  619. // This is implicit for LHS operands of the in operator
  620. // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
  621. // This step can be combined with c
  622. // c. If kPresent is true, then
  623. if (k in O) {
  624. // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
  625. kValue = O[ k ];
  626. // ii. Let mappedValue be the result of calling the Call internal method of callback
  627. // with T as the this value and argument list containing kValue, k, and O.
  628. mappedValue = callback.call(T, kValue, k, O);
  629. // iii. Call the DefineOwnProperty internal method of A with arguments
  630. // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
  631. // and false.
  632. // In browsers that support Object.defineProperty, use the following:
  633. // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
  634. // For best browser support, use the following:
  635. A[ k ] = mappedValue;
  636. }
  637. // d. Increase k by 1.
  638. k++;
  639. }
  640. // 9. return A
  641. return A;
  642. };
  643. }
  644. // Internet Explorer 8 and older does not support Array.filter, so we define it
  645. // here in that case.
  646. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
  647. if (!Array.prototype.filter) {
  648. Array.prototype.filter = function(fun /*, thisp */) {
  649. "use strict";
  650. if (this == null) {
  651. throw new TypeError();
  652. }
  653. var t = Object(this);
  654. var len = t.length >>> 0;
  655. if (typeof fun != "function") {
  656. throw new TypeError();
  657. }
  658. var res = [];
  659. var thisp = arguments[1];
  660. for (var i = 0; i < len; i++) {
  661. if (i in t) {
  662. var val = t[i]; // in case fun mutates this
  663. if (fun.call(thisp, val, i, t))
  664. res.push(val);
  665. }
  666. }
  667. return res;
  668. };
  669. }
  670. // Internet Explorer 8 and older does not support Object.keys, so we define it
  671. // here in that case.
  672. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
  673. if (!Object.keys) {
  674. Object.keys = (function () {
  675. var hasOwnProperty = Object.prototype.hasOwnProperty,
  676. hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
  677. dontEnums = [
  678. 'toString',
  679. 'toLocaleString',
  680. 'valueOf',
  681. 'hasOwnProperty',
  682. 'isPrototypeOf',
  683. 'propertyIsEnumerable',
  684. 'constructor'
  685. ],
  686. dontEnumsLength = dontEnums.length;
  687. return function (obj) {
  688. if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
  689. throw new TypeError('Object.keys called on non-object');
  690. }
  691. var result = [];
  692. for (var prop in obj) {
  693. if (hasOwnProperty.call(obj, prop)) result.push(prop);
  694. }
  695. if (hasDontEnumBug) {
  696. for (var i=0; i < dontEnumsLength; i++) {
  697. if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
  698. }
  699. }
  700. return result;
  701. }
  702. })()
  703. }
  704. // Internet Explorer 8 and older does not support Array.isArray,
  705. // so we define it here in that case.
  706. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray
  707. if(!Array.isArray) {
  708. Array.isArray = function (vArg) {
  709. return Object.prototype.toString.call(vArg) === "[object Array]";
  710. };
  711. }
  712. // Internet Explorer 8 and older does not support Function.bind,
  713. // so we define it here in that case.
  714. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
  715. if (!Function.prototype.bind) {
  716. Function.prototype.bind = function (oThis) {
  717. if (typeof this !== "function") {
  718. // closest thing possible to the ECMAScript 5 internal IsCallable function
  719. throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
  720. }
  721. var aArgs = Array.prototype.slice.call(arguments, 1),
  722. fToBind = this,
  723. fNOP = function () {},
  724. fBound = function () {
  725. return fToBind.apply(this instanceof fNOP && oThis
  726. ? this
  727. : oThis,
  728. aArgs.concat(Array.prototype.slice.call(arguments)));
  729. };
  730. fNOP.prototype = this.prototype;
  731. fBound.prototype = new fNOP();
  732. return fBound;
  733. };
  734. }