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.

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