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.

1278 lines
33 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. // utility functions
  2. // first check if moment.js is already loaded in the browser window, if so,
  3. // use this instance. Else, load via commonjs.
  4. var moment = require('./module/moment');
  5. /**
  6. * Test whether given object is a number
  7. * @param {*} object
  8. * @return {Boolean} isNumber
  9. */
  10. exports.isNumber = function(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. exports.isString = function(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. exports.isDate = function(object) {
  27. if (object instanceof Date) {
  28. return true;
  29. }
  30. else if (exports.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. exports.isDataTable = function(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. exports.randomUUID = function() {
  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. exports.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)) {
  84. a[prop] = other[prop];
  85. }
  86. }
  87. }
  88. return a;
  89. };
  90. /**
  91. * Extend object a with selected properties of object b or a series of objects
  92. * Only properties with defined values are copied
  93. * @param {Array.<String>} props
  94. * @param {Object} a
  95. * @param {... Object} b
  96. * @return {Object} a
  97. */
  98. exports.selectiveExtend = function (props, a, b) {
  99. if (!Array.isArray(props)) {
  100. throw new Error('Array with property names expected as first argument');
  101. }
  102. for (var i = 2; i < arguments.length; i++) {
  103. var other = arguments[i];
  104. for (var p = 0; p < props.length; p++) {
  105. var prop = props[p];
  106. if (other.hasOwnProperty(prop)) {
  107. a[prop] = other[prop];
  108. }
  109. }
  110. }
  111. return a;
  112. };
  113. /**
  114. * Extend object a with selected properties of object b or a series of objects
  115. * Only properties with defined values are copied
  116. * @param {Array.<String>} props
  117. * @param {Object} a
  118. * @param {... Object} b
  119. * @return {Object} a
  120. */
  121. exports.selectiveDeepExtend = function (props, a, b) {
  122. // TODO: add support for Arrays to deepExtend
  123. if (Array.isArray(b)) {
  124. throw new TypeError('Arrays are not supported by deepExtend');
  125. }
  126. for (var i = 2; i < arguments.length; i++) {
  127. var other = arguments[i];
  128. for (var p = 0; p < props.length; p++) {
  129. var prop = props[p];
  130. if (other.hasOwnProperty(prop)) {
  131. if (b[prop] && b[prop].constructor === Object) {
  132. if (a[prop] === undefined) {
  133. a[prop] = {};
  134. }
  135. if (a[prop].constructor === Object) {
  136. exports.deepExtend(a[prop], b[prop]);
  137. }
  138. else {
  139. a[prop] = b[prop];
  140. }
  141. } else if (Array.isArray(b[prop])) {
  142. throw new TypeError('Arrays are not supported by deepExtend');
  143. } else {
  144. a[prop] = b[prop];
  145. }
  146. }
  147. }
  148. }
  149. return a;
  150. };
  151. /**
  152. * Extend object a with selected properties of object b or a series of objects
  153. * Only properties with defined values are copied
  154. * @param {Array.<String>} props
  155. * @param {Object} a
  156. * @param {... Object} b
  157. * @return {Object} a
  158. */
  159. exports.selectiveNotDeepExtend = function (props, a, b) {
  160. // TODO: add support for Arrays to deepExtend
  161. if (Array.isArray(b)) {
  162. throw new TypeError('Arrays are not supported by deepExtend');
  163. }
  164. for (var prop in b) {
  165. if (b.hasOwnProperty(prop)) {
  166. if (props.indexOf(prop) == -1) {
  167. if (b[prop] && b[prop].constructor === Object) {
  168. if (a[prop] === undefined) {
  169. a[prop] = {};
  170. }
  171. if (a[prop].constructor === Object) {
  172. exports.deepExtend(a[prop], b[prop]);
  173. }
  174. else {
  175. a[prop] = b[prop];
  176. }
  177. } else if (Array.isArray(b[prop])) {
  178. throw new TypeError('Arrays are not supported by deepExtend');
  179. } else {
  180. a[prop] = b[prop];
  181. }
  182. }
  183. }
  184. }
  185. return a;
  186. };
  187. /**
  188. * Deep extend an object a with the properties of object b
  189. * @param {Object} a
  190. * @param {Object} b
  191. * @returns {Object}
  192. */
  193. exports.deepExtend = function(a, b) {
  194. // TODO: add support for Arrays to deepExtend
  195. if (Array.isArray(b)) {
  196. throw new TypeError('Arrays are not supported by deepExtend');
  197. }
  198. for (var prop in b) {
  199. if (b.hasOwnProperty(prop)) {
  200. if (b[prop] && b[prop].constructor === Object) {
  201. if (a[prop] === undefined) {
  202. a[prop] = {};
  203. }
  204. if (a[prop].constructor === Object) {
  205. exports.deepExtend(a[prop], b[prop]);
  206. }
  207. else {
  208. a[prop] = b[prop];
  209. }
  210. } else if (Array.isArray(b[prop])) {
  211. throw new TypeError('Arrays are not supported by deepExtend');
  212. } else {
  213. a[prop] = b[prop];
  214. }
  215. }
  216. }
  217. return a;
  218. };
  219. /**
  220. * Test whether all elements in two arrays are equal.
  221. * @param {Array} a
  222. * @param {Array} b
  223. * @return {boolean} Returns true if both arrays have the same length and same
  224. * elements.
  225. */
  226. exports.equalArray = function (a, b) {
  227. if (a.length != b.length) return false;
  228. for (var i = 0, len = a.length; i < len; i++) {
  229. if (a[i] != b[i]) return false;
  230. }
  231. return true;
  232. };
  233. /**
  234. * Convert an object to another type
  235. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  236. * @param {String | undefined} type Name of the type. Available types:
  237. * 'Boolean', 'Number', 'String',
  238. * 'Date', 'Moment', ISODate', 'ASPDate'.
  239. * @return {*} object
  240. * @throws Error
  241. */
  242. exports.convert = function(object, type) {
  243. var match;
  244. if (object === undefined) {
  245. return undefined;
  246. }
  247. if (object === null) {
  248. return null;
  249. }
  250. if (!type) {
  251. return object;
  252. }
  253. if (!(typeof type === 'string') && !(type instanceof String)) {
  254. throw new Error('Type must be a string');
  255. }
  256. //noinspection FallthroughInSwitchStatementJS
  257. switch (type) {
  258. case 'boolean':
  259. case 'Boolean':
  260. return Boolean(object);
  261. case 'number':
  262. case 'Number':
  263. return Number(object.valueOf());
  264. case 'string':
  265. case 'String':
  266. return String(object);
  267. case 'Date':
  268. if (exports.isNumber(object)) {
  269. return new Date(object);
  270. }
  271. if (object instanceof Date) {
  272. return new Date(object.valueOf());
  273. }
  274. else if (moment.isMoment(object)) {
  275. return new Date(object.valueOf());
  276. }
  277. if (exports.isString(object)) {
  278. match = ASPDateRegex.exec(object);
  279. if (match) {
  280. // object is an ASP date
  281. return new Date(Number(match[1])); // parse number
  282. }
  283. else {
  284. return moment(object).toDate(); // parse string
  285. }
  286. }
  287. else {
  288. throw new Error(
  289. 'Cannot convert object of type ' + exports.getType(object) +
  290. ' to type Date');
  291. }
  292. case 'Moment':
  293. if (exports.isNumber(object)) {
  294. return moment(object);
  295. }
  296. if (object instanceof Date) {
  297. return moment(object.valueOf());
  298. }
  299. else if (moment.isMoment(object)) {
  300. return moment(object);
  301. }
  302. if (exports.isString(object)) {
  303. match = ASPDateRegex.exec(object);
  304. if (match) {
  305. // object is an ASP date
  306. return moment(Number(match[1])); // parse number
  307. }
  308. else {
  309. return moment(object); // parse string
  310. }
  311. }
  312. else {
  313. throw new Error(
  314. 'Cannot convert object of type ' + exports.getType(object) +
  315. ' to type Date');
  316. }
  317. case 'ISODate':
  318. if (exports.isNumber(object)) {
  319. return new Date(object);
  320. }
  321. else if (object instanceof Date) {
  322. return object.toISOString();
  323. }
  324. else if (moment.isMoment(object)) {
  325. return object.toDate().toISOString();
  326. }
  327. else if (exports.isString(object)) {
  328. match = ASPDateRegex.exec(object);
  329. if (match) {
  330. // object is an ASP date
  331. return new Date(Number(match[1])).toISOString(); // parse number
  332. }
  333. else {
  334. return new Date(object).toISOString(); // parse string
  335. }
  336. }
  337. else {
  338. throw new Error(
  339. 'Cannot convert object of type ' + exports.getType(object) +
  340. ' to type ISODate');
  341. }
  342. case 'ASPDate':
  343. if (exports.isNumber(object)) {
  344. return '/Date(' + object + ')/';
  345. }
  346. else if (object instanceof Date) {
  347. return '/Date(' + object.valueOf() + ')/';
  348. }
  349. else if (exports.isString(object)) {
  350. match = ASPDateRegex.exec(object);
  351. var value;
  352. if (match) {
  353. // object is an ASP date
  354. value = new Date(Number(match[1])).valueOf(); // parse number
  355. }
  356. else {
  357. value = new Date(object).valueOf(); // parse string
  358. }
  359. return '/Date(' + value + ')/';
  360. }
  361. else {
  362. throw new Error(
  363. 'Cannot convert object of type ' + exports.getType(object) +
  364. ' to type ASPDate');
  365. }
  366. default:
  367. throw new Error('Unknown type "' + type + '"');
  368. }
  369. };
  370. // parse ASP.Net Date pattern,
  371. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  372. // code from http://momentjs.com/
  373. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  374. /**
  375. * Get the type of an object, for example exports.getType([]) returns 'Array'
  376. * @param {*} object
  377. * @return {String} type
  378. */
  379. exports.getType = function(object) {
  380. var type = typeof object;
  381. if (type == 'object') {
  382. if (object == null) {
  383. return 'null';
  384. }
  385. if (object instanceof Boolean) {
  386. return 'Boolean';
  387. }
  388. if (object instanceof Number) {
  389. return 'Number';
  390. }
  391. if (object instanceof String) {
  392. return 'String';
  393. }
  394. if (Array.isArray(object)) {
  395. return 'Array';
  396. }
  397. if (object instanceof Date) {
  398. return 'Date';
  399. }
  400. return 'Object';
  401. }
  402. else if (type == 'number') {
  403. return 'Number';
  404. }
  405. else if (type == 'boolean') {
  406. return 'Boolean';
  407. }
  408. else if (type == 'string') {
  409. return 'String';
  410. }
  411. return type;
  412. };
  413. /**
  414. * Retrieve the absolute left value of a DOM element
  415. * @param {Element} elem A dom element, for example a div
  416. * @return {number} left The absolute left position of this element
  417. * in the browser page.
  418. */
  419. exports.getAbsoluteLeft = function(elem) {
  420. return elem.getBoundingClientRect().left + window.pageXOffset;
  421. };
  422. /**
  423. * Retrieve the absolute top value of a DOM element
  424. * @param {Element} elem A dom element, for example a div
  425. * @return {number} top The absolute top position of this element
  426. * in the browser page.
  427. */
  428. exports.getAbsoluteTop = function(elem) {
  429. return elem.getBoundingClientRect().top + window.pageYOffset;
  430. };
  431. /**
  432. * add a className to the given elements style
  433. * @param {Element} elem
  434. * @param {String} className
  435. */
  436. exports.addClassName = function(elem, className) {
  437. var classes = elem.className.split(' ');
  438. if (classes.indexOf(className) == -1) {
  439. classes.push(className); // add the class to the array
  440. elem.className = classes.join(' ');
  441. }
  442. };
  443. /**
  444. * add a className to the given elements style
  445. * @param {Element} elem
  446. * @param {String} className
  447. */
  448. exports.removeClassName = function(elem, className) {
  449. var classes = elem.className.split(' ');
  450. var index = classes.indexOf(className);
  451. if (index != -1) {
  452. classes.splice(index, 1); // remove the class from the array
  453. elem.className = classes.join(' ');
  454. }
  455. };
  456. /**
  457. * For each method for both arrays and objects.
  458. * In case of an array, the built-in Array.forEach() is applied.
  459. * In case of an Object, the method loops over all properties of the object.
  460. * @param {Object | Array} object An Object or Array
  461. * @param {function} callback Callback method, called for each item in
  462. * the object or array with three parameters:
  463. * callback(value, index, object)
  464. */
  465. exports.forEach = function(object, callback) {
  466. var i,
  467. len;
  468. if (Array.isArray(object)) {
  469. // array
  470. for (i = 0, len = object.length; i < len; i++) {
  471. callback(object[i], i, object);
  472. }
  473. }
  474. else {
  475. // object
  476. for (i in object) {
  477. if (object.hasOwnProperty(i)) {
  478. callback(object[i], i, object);
  479. }
  480. }
  481. }
  482. };
  483. /**
  484. * Convert an object into an array: all objects properties are put into the
  485. * array. The resulting array is unordered.
  486. * @param {Object} object
  487. * @param {Array} array
  488. */
  489. exports.toArray = function(object) {
  490. var array = [];
  491. for (var prop in object) {
  492. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  493. }
  494. return array;
  495. }
  496. /**
  497. * Update a property in an object
  498. * @param {Object} object
  499. * @param {String} key
  500. * @param {*} value
  501. * @return {Boolean} changed
  502. */
  503. exports.updateProperty = function(object, key, value) {
  504. if (object[key] !== value) {
  505. object[key] = value;
  506. return true;
  507. }
  508. else {
  509. return false;
  510. }
  511. };
  512. /**
  513. * Add and event listener. Works for all browsers
  514. * @param {Element} element An html element
  515. * @param {string} action The action, for example "click",
  516. * without the prefix "on"
  517. * @param {function} listener The callback function to be executed
  518. * @param {boolean} [useCapture]
  519. */
  520. exports.addEventListener = function(element, action, listener, useCapture) {
  521. if (element.addEventListener) {
  522. if (useCapture === undefined)
  523. useCapture = false;
  524. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  525. action = "DOMMouseScroll"; // For Firefox
  526. }
  527. element.addEventListener(action, listener, useCapture);
  528. } else {
  529. element.attachEvent("on" + action, listener); // IE browsers
  530. }
  531. };
  532. /**
  533. * Remove an event listener from an element
  534. * @param {Element} element An html dom element
  535. * @param {string} action The name of the event, for example "mousedown"
  536. * @param {function} listener The listener function
  537. * @param {boolean} [useCapture]
  538. */
  539. exports.removeEventListener = function(element, action, listener, useCapture) {
  540. if (element.removeEventListener) {
  541. // non-IE browsers
  542. if (useCapture === undefined)
  543. useCapture = false;
  544. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  545. action = "DOMMouseScroll"; // For Firefox
  546. }
  547. element.removeEventListener(action, listener, useCapture);
  548. } else {
  549. // IE browsers
  550. element.detachEvent("on" + action, listener);
  551. }
  552. };
  553. /**
  554. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  555. */
  556. exports.preventDefault = function (event) {
  557. if (!event)
  558. event = window.event;
  559. if (event.preventDefault) {
  560. event.preventDefault(); // non-IE browsers
  561. }
  562. else {
  563. event.returnValue = false; // IE browsers
  564. }
  565. };
  566. /**
  567. * Get HTML element which is the target of the event
  568. * @param {Event} event
  569. * @return {Element} target element
  570. */
  571. exports.getTarget = function(event) {
  572. // code from http://www.quirksmode.org/js/events_properties.html
  573. if (!event) {
  574. event = window.event;
  575. }
  576. var target;
  577. if (event.target) {
  578. target = event.target;
  579. }
  580. else if (event.srcElement) {
  581. target = event.srcElement;
  582. }
  583. if (target.nodeType != undefined && target.nodeType == 3) {
  584. // defeat Safari bug
  585. target = target.parentNode;
  586. }
  587. return target;
  588. };
  589. exports.option = {};
  590. /**
  591. * Convert a value into a boolean
  592. * @param {Boolean | function | undefined} value
  593. * @param {Boolean} [defaultValue]
  594. * @returns {Boolean} bool
  595. */
  596. exports.option.asBoolean = function (value, defaultValue) {
  597. if (typeof value == 'function') {
  598. value = value();
  599. }
  600. if (value != null) {
  601. return (value != false);
  602. }
  603. return defaultValue || null;
  604. };
  605. /**
  606. * Convert a value into a number
  607. * @param {Boolean | function | undefined} value
  608. * @param {Number} [defaultValue]
  609. * @returns {Number} number
  610. */
  611. exports.option.asNumber = function (value, defaultValue) {
  612. if (typeof value == 'function') {
  613. value = value();
  614. }
  615. if (value != null) {
  616. return Number(value) || defaultValue || null;
  617. }
  618. return defaultValue || null;
  619. };
  620. /**
  621. * Convert a value into a string
  622. * @param {String | function | undefined} value
  623. * @param {String} [defaultValue]
  624. * @returns {String} str
  625. */
  626. exports.option.asString = function (value, defaultValue) {
  627. if (typeof value == 'function') {
  628. value = value();
  629. }
  630. if (value != null) {
  631. return String(value);
  632. }
  633. return defaultValue || null;
  634. };
  635. /**
  636. * Convert a size or location into a string with pixels or a percentage
  637. * @param {String | Number | function | undefined} value
  638. * @param {String} [defaultValue]
  639. * @returns {String} size
  640. */
  641. exports.option.asSize = function (value, defaultValue) {
  642. if (typeof value == 'function') {
  643. value = value();
  644. }
  645. if (exports.isString(value)) {
  646. return value;
  647. }
  648. else if (exports.isNumber(value)) {
  649. return value + 'px';
  650. }
  651. else {
  652. return defaultValue || null;
  653. }
  654. };
  655. /**
  656. * Convert a value into a DOM element
  657. * @param {HTMLElement | function | undefined} value
  658. * @param {HTMLElement} [defaultValue]
  659. * @returns {HTMLElement | null} dom
  660. */
  661. exports.option.asElement = function (value, defaultValue) {
  662. if (typeof value == 'function') {
  663. value = value();
  664. }
  665. return value || defaultValue || null;
  666. };
  667. exports.GiveDec = function(Hex) {
  668. var Value;
  669. if (Hex == "A")
  670. Value = 10;
  671. else if (Hex == "B")
  672. Value = 11;
  673. else if (Hex == "C")
  674. Value = 12;
  675. else if (Hex == "D")
  676. Value = 13;
  677. else if (Hex == "E")
  678. Value = 14;
  679. else if (Hex == "F")
  680. Value = 15;
  681. else
  682. Value = eval(Hex);
  683. return Value;
  684. };
  685. exports.GiveHex = function(Dec) {
  686. var Value;
  687. if(Dec == 10)
  688. Value = "A";
  689. else if (Dec == 11)
  690. Value = "B";
  691. else if (Dec == 12)
  692. Value = "C";
  693. else if (Dec == 13)
  694. Value = "D";
  695. else if (Dec == 14)
  696. Value = "E";
  697. else if (Dec == 15)
  698. Value = "F";
  699. else
  700. Value = "" + Dec;
  701. return Value;
  702. };
  703. /**
  704. * Parse a color property into an object with border, background, and
  705. * highlight colors
  706. * @param {Object | String} color
  707. * @return {Object} colorObject
  708. */
  709. exports.parseColor = function(color) {
  710. var c;
  711. if (exports.isString(color)) {
  712. if (exports.isValidRGB(color)) {
  713. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  714. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  715. }
  716. if (exports.isValidHex(color)) {
  717. var hsv = exports.hexToHSV(color);
  718. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  719. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  720. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  721. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  722. c = {
  723. background: color,
  724. border:darkerColorHex,
  725. highlight: {
  726. background:lighterColorHex,
  727. border:darkerColorHex
  728. },
  729. hover: {
  730. background:lighterColorHex,
  731. border:darkerColorHex
  732. }
  733. };
  734. }
  735. else {
  736. c = {
  737. background:color,
  738. border:color,
  739. highlight: {
  740. background:color,
  741. border:color
  742. },
  743. hover: {
  744. background:color,
  745. border:color
  746. }
  747. };
  748. }
  749. }
  750. else {
  751. c = {};
  752. c.background = color.background || 'white';
  753. c.border = color.border || c.background;
  754. if (exports.isString(color.highlight)) {
  755. c.highlight = {
  756. border: color.highlight,
  757. background: color.highlight
  758. }
  759. }
  760. else {
  761. c.highlight = {};
  762. c.highlight.background = color.highlight && color.highlight.background || c.background;
  763. c.highlight.border = color.highlight && color.highlight.border || c.border;
  764. }
  765. if (exports.isString(color.hover)) {
  766. c.hover = {
  767. border: color.hover,
  768. background: color.hover
  769. }
  770. }
  771. else {
  772. c.hover = {};
  773. c.hover.background = color.hover && color.hover.background || c.background;
  774. c.hover.border = color.hover && color.hover.border || c.border;
  775. }
  776. }
  777. return c;
  778. };
  779. /**
  780. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  781. *
  782. * @param {String} hex
  783. * @returns {{r: *, g: *, b: *}}
  784. */
  785. exports.hexToRGB = function(hex) {
  786. hex = hex.replace("#","").toUpperCase();
  787. var a = exports.GiveDec(hex.substring(0, 1));
  788. var b = exports.GiveDec(hex.substring(1, 2));
  789. var c = exports.GiveDec(hex.substring(2, 3));
  790. var d = exports.GiveDec(hex.substring(3, 4));
  791. var e = exports.GiveDec(hex.substring(4, 5));
  792. var f = exports.GiveDec(hex.substring(5, 6));
  793. var r = (a * 16) + b;
  794. var g = (c * 16) + d;
  795. var b = (e * 16) + f;
  796. return {r:r,g:g,b:b};
  797. };
  798. exports.RGBToHex = function(red,green,blue) {
  799. var a = exports.GiveHex(Math.floor(red / 16));
  800. var b = exports.GiveHex(red % 16);
  801. var c = exports.GiveHex(Math.floor(green / 16));
  802. var d = exports.GiveHex(green % 16);
  803. var e = exports.GiveHex(Math.floor(blue / 16));
  804. var f = exports.GiveHex(blue % 16);
  805. var hex = a + b + c + d + e + f;
  806. return "#" + hex;
  807. };
  808. /**
  809. * http://www.javascripter.net/faq/rgb2hsv.htm
  810. *
  811. * @param red
  812. * @param green
  813. * @param blue
  814. * @returns {*}
  815. * @constructor
  816. */
  817. exports.RGBToHSV = function(red,green,blue) {
  818. red=red/255; green=green/255; blue=blue/255;
  819. var minRGB = Math.min(red,Math.min(green,blue));
  820. var maxRGB = Math.max(red,Math.max(green,blue));
  821. // Black-gray-white
  822. if (minRGB == maxRGB) {
  823. return {h:0,s:0,v:minRGB};
  824. }
  825. // Colors other than black-gray-white:
  826. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  827. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  828. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  829. var saturation = (maxRGB - minRGB)/maxRGB;
  830. var value = maxRGB;
  831. return {h:hue,s:saturation,v:value};
  832. };
  833. var cssUtil = {
  834. // split a string with css styles into an object with key/values
  835. split: function (cssText) {
  836. var styles = {};
  837. cssText.split(';').forEach(function (style) {
  838. if (style.trim() != '') {
  839. var parts = style.split(':');
  840. var key = parts[0].trim();
  841. var value = parts[1].trim();
  842. styles[key] = value;
  843. }
  844. });
  845. return styles;
  846. },
  847. // build a css text string from an object with key/values
  848. join: function (styles) {
  849. return Object.keys(styles)
  850. .map(function (key) {
  851. return key + ': ' + styles[key];
  852. })
  853. .join('; ');
  854. }
  855. };
  856. /**
  857. * Append a string with css styles to an element
  858. * @param {Element} element
  859. * @param {String} cssText
  860. */
  861. exports.addCssText = function (element, cssText) {
  862. var currentStyles = cssUtil.split(element.style.cssText);
  863. var newStyles = cssUtil.split(cssText);
  864. var styles = exports.extend(currentStyles, newStyles);
  865. element.style.cssText = cssUtil.join(styles);
  866. };
  867. /**
  868. * Remove a string with css styles from an element
  869. * @param {Element} element
  870. * @param {String} cssText
  871. */
  872. exports.removeCssText = function (element, cssText) {
  873. var styles = cssUtil.split(element.style.cssText);
  874. var removeStyles = cssUtil.split(cssText);
  875. for (var key in removeStyles) {
  876. if (removeStyles.hasOwnProperty(key)) {
  877. delete styles[key];
  878. }
  879. }
  880. element.style.cssText = cssUtil.join(styles);
  881. };
  882. /**
  883. * https://gist.github.com/mjijackson/5311256
  884. * @param h
  885. * @param s
  886. * @param v
  887. * @returns {{r: number, g: number, b: number}}
  888. * @constructor
  889. */
  890. exports.HSVToRGB = function(h, s, v) {
  891. var r, g, b;
  892. var i = Math.floor(h * 6);
  893. var f = h * 6 - i;
  894. var p = v * (1 - s);
  895. var q = v * (1 - f * s);
  896. var t = v * (1 - (1 - f) * s);
  897. switch (i % 6) {
  898. case 0: r = v, g = t, b = p; break;
  899. case 1: r = q, g = v, b = p; break;
  900. case 2: r = p, g = v, b = t; break;
  901. case 3: r = p, g = q, b = v; break;
  902. case 4: r = t, g = p, b = v; break;
  903. case 5: r = v, g = p, b = q; break;
  904. }
  905. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  906. };
  907. exports.HSVToHex = function(h, s, v) {
  908. var rgb = exports.HSVToRGB(h, s, v);
  909. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  910. };
  911. exports.hexToHSV = function(hex) {
  912. var rgb = exports.hexToRGB(hex);
  913. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  914. };
  915. exports.isValidHex = function(hex) {
  916. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  917. return isOk;
  918. };
  919. exports.isValidRGB = function(rgb) {
  920. rgb = rgb.replace(" ","");
  921. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  922. return isOk;
  923. }
  924. /**
  925. * This recursively redirects the prototype of JSON objects to the referenceObject
  926. * This is used for default options.
  927. *
  928. * @param referenceObject
  929. * @returns {*}
  930. */
  931. exports.selectiveBridgeObject = function(fields, referenceObject) {
  932. if (typeof referenceObject == "object") {
  933. var objectTo = Object.create(referenceObject);
  934. for (var i = 0; i < fields.length; i++) {
  935. if (referenceObject.hasOwnProperty(fields[i])) {
  936. if (typeof referenceObject[fields[i]] == "object") {
  937. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  938. }
  939. }
  940. }
  941. return objectTo;
  942. }
  943. else {
  944. return null;
  945. }
  946. };
  947. /**
  948. * This recursively redirects the prototype of JSON objects to the referenceObject
  949. * This is used for default options.
  950. *
  951. * @param referenceObject
  952. * @returns {*}
  953. */
  954. exports.bridgeObject = function(referenceObject) {
  955. if (typeof referenceObject == "object") {
  956. var objectTo = Object.create(referenceObject);
  957. for (var i in referenceObject) {
  958. if (referenceObject.hasOwnProperty(i)) {
  959. if (typeof referenceObject[i] == "object") {
  960. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  961. }
  962. }
  963. }
  964. return objectTo;
  965. }
  966. else {
  967. return null;
  968. }
  969. };
  970. /**
  971. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  972. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  973. *
  974. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  975. * @param [object] options | options
  976. * @param [String] option | this is the option key in the options argument
  977. * @private
  978. */
  979. exports.mergeOptions = function (mergeTarget, options, option) {
  980. if (options[option] !== undefined) {
  981. if (typeof options[option] == 'boolean') {
  982. mergeTarget[option].enabled = options[option];
  983. }
  984. else {
  985. mergeTarget[option].enabled = true;
  986. for (var prop in options[option]) {
  987. if (options[option].hasOwnProperty(prop)) {
  988. mergeTarget[option][prop] = options[option][prop];
  989. }
  990. }
  991. }
  992. }
  993. }
  994. /**
  995. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  996. * this function will then iterate in both directions over this sorted list to find all visible items.
  997. *
  998. * @param {Item[]} orderedItems | Items ordered by start
  999. * @param {function} searchFunction | -1 is lower, 0 is found, 1 is higher
  1000. * @param {String} field
  1001. * @param {String} field2
  1002. * @returns {number}
  1003. * @private
  1004. */
  1005. exports.binarySearchCustom = function(orderedItems, searchFunction, field, field2) {
  1006. var maxIterations = 10000;
  1007. var iteration = 0;
  1008. var low = 0;
  1009. var high = orderedItems.length - 1;
  1010. while (low <= high && iteration < maxIterations) {
  1011. var middle = Math.floor((low + high) / 2);
  1012. var item = orderedItems[middle];
  1013. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1014. var searchResult = searchFunction(value);
  1015. if (searchResult == 0) { // jihaa, found a visible item!
  1016. return middle;
  1017. }
  1018. else if (searchResult == -1) { // it is too small --> increase low
  1019. low = middle + 1;
  1020. }
  1021. else { // it is too big --> decrease high
  1022. high = middle - 1;
  1023. }
  1024. iteration++;
  1025. }
  1026. return -1;
  1027. };
  1028. /**
  1029. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1030. * two values, we return either the one before or the one after, depending on user input
  1031. * If it is found, we return the index, else -1.
  1032. *
  1033. * @param {Array} orderedItems
  1034. * @param {{start: number, end: number}} target
  1035. * @param {String} field
  1036. * @param {String} sidePreference 'before' or 'after'
  1037. * @returns {number}
  1038. * @private
  1039. */
  1040. exports.binarySearchValue = function(orderedItems, target, field, sidePreference) {
  1041. var maxIterations = 10000;
  1042. var iteration = 0;
  1043. var low = 0;
  1044. var high = orderedItems.length - 1;
  1045. var prevValue, value, nextValue, middle;
  1046. while (low <= high && iteration < maxIterations) {
  1047. // get a new guess
  1048. middle = Math.floor(0.5*(high+low));
  1049. prevValue = orderedItems[Math.max(0,middle - 1)][field];
  1050. value = orderedItems[middle][field];
  1051. nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
  1052. if (value == target) { // we found the target
  1053. return middle;
  1054. }
  1055. else if (prevValue < target && value > target) { // target is in between of the previous and the current
  1056. return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
  1057. }
  1058. else if (value < target && nextValue > target) { // target is in between of the current and the next
  1059. return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
  1060. }
  1061. else { // didnt find the target, we need to change our boundaries.
  1062. if (value < target) { // it is too small --> increase low
  1063. low = middle + 1;
  1064. }
  1065. else { // it is too big --> decrease high
  1066. high = middle - 1;
  1067. }
  1068. }
  1069. iteration++;
  1070. }
  1071. // didnt find anything. Return -1.
  1072. return -1;
  1073. };
  1074. /**
  1075. * Quadratic ease-in-out
  1076. * http://gizma.com/easing/
  1077. * @param {number} t Current time
  1078. * @param {number} start Start value
  1079. * @param {number} end End value
  1080. * @param {number} duration Duration
  1081. * @returns {number} Value corresponding with current time
  1082. */
  1083. exports.easeInOutQuad = function (t, start, end, duration) {
  1084. var change = end - start;
  1085. t /= duration/2;
  1086. if (t < 1) return change/2*t*t + start;
  1087. t--;
  1088. return -change/2 * (t*(t-2) - 1) + start;
  1089. };
  1090. /*
  1091. * Easing Functions - inspired from http://gizma.com/easing/
  1092. * only considering the t value for the range [0, 1] => [0, 1]
  1093. * https://gist.github.com/gre/1650294
  1094. */
  1095. exports.easingFunctions = {
  1096. // no easing, no acceleration
  1097. linear: function (t) {
  1098. return t
  1099. },
  1100. // accelerating from zero velocity
  1101. easeInQuad: function (t) {
  1102. return t * t
  1103. },
  1104. // decelerating to zero velocity
  1105. easeOutQuad: function (t) {
  1106. return t * (2 - t)
  1107. },
  1108. // acceleration until halfway, then deceleration
  1109. easeInOutQuad: function (t) {
  1110. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1111. },
  1112. // accelerating from zero velocity
  1113. easeInCubic: function (t) {
  1114. return t * t * t
  1115. },
  1116. // decelerating to zero velocity
  1117. easeOutCubic: function (t) {
  1118. return (--t) * t * t + 1
  1119. },
  1120. // acceleration until halfway, then deceleration
  1121. easeInOutCubic: function (t) {
  1122. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1123. },
  1124. // accelerating from zero velocity
  1125. easeInQuart: function (t) {
  1126. return t * t * t * t
  1127. },
  1128. // decelerating to zero velocity
  1129. easeOutQuart: function (t) {
  1130. return 1 - (--t) * t * t * t
  1131. },
  1132. // acceleration until halfway, then deceleration
  1133. easeInOutQuart: function (t) {
  1134. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1135. },
  1136. // accelerating from zero velocity
  1137. easeInQuint: function (t) {
  1138. return t * t * t * t * t
  1139. },
  1140. // decelerating to zero velocity
  1141. easeOutQuint: function (t) {
  1142. return 1 + (--t) * t * t * t * t
  1143. },
  1144. // acceleration until halfway, then deceleration
  1145. easeInOutQuint: function (t) {
  1146. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1147. }
  1148. };