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.

1213 lines
31 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 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. * Get HTML element which is the target of the event
  520. * @param {Event} event
  521. * @return {Element} target element
  522. */
  523. exports.getTarget = function(event) {
  524. // code from http://www.quirksmode.org/js/events_properties.html
  525. if (!event) {
  526. event = window.event;
  527. }
  528. var target;
  529. if (event.target) {
  530. target = event.target;
  531. }
  532. else if (event.srcElement) {
  533. target = event.srcElement;
  534. }
  535. if (target.nodeType != undefined && target.nodeType == 3) {
  536. // defeat Safari bug
  537. target = target.parentNode;
  538. }
  539. return target;
  540. };
  541. /**
  542. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  543. * @param {Element} element
  544. * @param {Event} event
  545. */
  546. exports.fakeGesture = function(element, event) {
  547. var eventType = null;
  548. // for hammer.js 1.0.5
  549. var gesture = Hammer.event.collectEventData(this, eventType, event);
  550. // for hammer.js 1.0.6
  551. //var touches = Hammer.event.getTouchList(event, eventType);
  552. // var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  553. // on IE in standards mode, no touches are recognized by hammer.js,
  554. // resulting in NaN values for center.pageX and center.pageY
  555. if (isNaN(gesture.center.pageX)) {
  556. gesture.center.pageX = event.pageX;
  557. }
  558. if (isNaN(gesture.center.pageY)) {
  559. gesture.center.pageY = event.pageY;
  560. }
  561. return gesture;
  562. };
  563. exports.option = {};
  564. /**
  565. * Convert a value into a boolean
  566. * @param {Boolean | function | undefined} value
  567. * @param {Boolean} [defaultValue]
  568. * @returns {Boolean} bool
  569. */
  570. exports.option.asBoolean = function (value, defaultValue) {
  571. if (typeof value == 'function') {
  572. value = value();
  573. }
  574. if (value != null) {
  575. return (value != false);
  576. }
  577. return defaultValue || null;
  578. };
  579. /**
  580. * Convert a value into a number
  581. * @param {Boolean | function | undefined} value
  582. * @param {Number} [defaultValue]
  583. * @returns {Number} number
  584. */
  585. exports.option.asNumber = function (value, defaultValue) {
  586. if (typeof value == 'function') {
  587. value = value();
  588. }
  589. if (value != null) {
  590. return Number(value) || defaultValue || null;
  591. }
  592. return defaultValue || null;
  593. };
  594. /**
  595. * Convert a value into a string
  596. * @param {String | function | undefined} value
  597. * @param {String} [defaultValue]
  598. * @returns {String} str
  599. */
  600. exports.option.asString = function (value, defaultValue) {
  601. if (typeof value == 'function') {
  602. value = value();
  603. }
  604. if (value != null) {
  605. return String(value);
  606. }
  607. return defaultValue || null;
  608. };
  609. /**
  610. * Convert a size or location into a string with pixels or a percentage
  611. * @param {String | Number | function | undefined} value
  612. * @param {String} [defaultValue]
  613. * @returns {String} size
  614. */
  615. exports.option.asSize = function (value, defaultValue) {
  616. if (typeof value == 'function') {
  617. value = value();
  618. }
  619. if (exports.isString(value)) {
  620. return value;
  621. }
  622. else if (exports.isNumber(value)) {
  623. return value + 'px';
  624. }
  625. else {
  626. return defaultValue || null;
  627. }
  628. };
  629. /**
  630. * Convert a value into a DOM element
  631. * @param {HTMLElement | function | undefined} value
  632. * @param {HTMLElement} [defaultValue]
  633. * @returns {HTMLElement | null} dom
  634. */
  635. exports.option.asElement = function (value, defaultValue) {
  636. if (typeof value == 'function') {
  637. value = value();
  638. }
  639. return value || defaultValue || null;
  640. };
  641. exports.GiveDec = function(Hex) {
  642. var Value;
  643. if (Hex == "A")
  644. Value = 10;
  645. else if (Hex == "B")
  646. Value = 11;
  647. else if (Hex == "C")
  648. Value = 12;
  649. else if (Hex == "D")
  650. Value = 13;
  651. else if (Hex == "E")
  652. Value = 14;
  653. else if (Hex == "F")
  654. Value = 15;
  655. else
  656. Value = eval(Hex);
  657. return Value;
  658. };
  659. exports.GiveHex = function(Dec) {
  660. var Value;
  661. if(Dec == 10)
  662. Value = "A";
  663. else if (Dec == 11)
  664. Value = "B";
  665. else if (Dec == 12)
  666. Value = "C";
  667. else if (Dec == 13)
  668. Value = "D";
  669. else if (Dec == 14)
  670. Value = "E";
  671. else if (Dec == 15)
  672. Value = "F";
  673. else
  674. Value = "" + Dec;
  675. return Value;
  676. };
  677. /**
  678. * Parse a color property into an object with border, background, and
  679. * highlight colors
  680. * @param {Object | String} color
  681. * @return {Object} colorObject
  682. */
  683. exports.parseColor = function(color) {
  684. var c;
  685. if (exports.isString(color)) {
  686. if (exports.isValidHex(color)) {
  687. var hsv = exports.hexToHSV(color);
  688. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  689. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  690. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  691. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  692. c = {
  693. background: color,
  694. border:darkerColorHex,
  695. highlight: {
  696. background:lighterColorHex,
  697. border:darkerColorHex
  698. },
  699. hover: {
  700. background:lighterColorHex,
  701. border:darkerColorHex
  702. }
  703. };
  704. }
  705. else {
  706. c = {
  707. background:color,
  708. border:color,
  709. highlight: {
  710. background:color,
  711. border:color
  712. },
  713. hover: {
  714. background:color,
  715. border:color
  716. }
  717. };
  718. }
  719. }
  720. else {
  721. c = {};
  722. c.background = color.background || 'white';
  723. c.border = color.border || c.background;
  724. if (exports.isString(color.highlight)) {
  725. c.highlight = {
  726. border: color.highlight,
  727. background: color.highlight
  728. }
  729. }
  730. else {
  731. c.highlight = {};
  732. c.highlight.background = color.highlight && color.highlight.background || c.background;
  733. c.highlight.border = color.highlight && color.highlight.border || c.border;
  734. }
  735. if (exports.isString(color.hover)) {
  736. c.hover = {
  737. border: color.hover,
  738. background: color.hover
  739. }
  740. }
  741. else {
  742. c.hover = {};
  743. c.hover.background = color.hover && color.hover.background || c.background;
  744. c.hover.border = color.hover && color.hover.border || c.border;
  745. }
  746. }
  747. return c;
  748. };
  749. /**
  750. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  751. *
  752. * @param {String} hex
  753. * @returns {{r: *, g: *, b: *}}
  754. */
  755. exports.hexToRGB = function(hex) {
  756. hex = hex.replace("#","").toUpperCase();
  757. var a = exports.GiveDec(hex.substring(0, 1));
  758. var b = exports.GiveDec(hex.substring(1, 2));
  759. var c = exports.GiveDec(hex.substring(2, 3));
  760. var d = exports.GiveDec(hex.substring(3, 4));
  761. var e = exports.GiveDec(hex.substring(4, 5));
  762. var f = exports.GiveDec(hex.substring(5, 6));
  763. var r = (a * 16) + b;
  764. var g = (c * 16) + d;
  765. var b = (e * 16) + f;
  766. return {r:r,g:g,b:b};
  767. };
  768. exports.RGBToHex = function(red,green,blue) {
  769. var a = exports.GiveHex(Math.floor(red / 16));
  770. var b = exports.GiveHex(red % 16);
  771. var c = exports.GiveHex(Math.floor(green / 16));
  772. var d = exports.GiveHex(green % 16);
  773. var e = exports.GiveHex(Math.floor(blue / 16));
  774. var f = exports.GiveHex(blue % 16);
  775. var hex = a + b + c + d + e + f;
  776. return "#" + hex;
  777. };
  778. /**
  779. * http://www.javascripter.net/faq/rgb2hsv.htm
  780. *
  781. * @param red
  782. * @param green
  783. * @param blue
  784. * @returns {*}
  785. * @constructor
  786. */
  787. exports.RGBToHSV = function(red,green,blue) {
  788. red=red/255; green=green/255; blue=blue/255;
  789. var minRGB = Math.min(red,Math.min(green,blue));
  790. var maxRGB = Math.max(red,Math.max(green,blue));
  791. // Black-gray-white
  792. if (minRGB == maxRGB) {
  793. return {h:0,s:0,v:minRGB};
  794. }
  795. // Colors other than black-gray-white:
  796. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  797. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  798. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  799. var saturation = (maxRGB - minRGB)/maxRGB;
  800. var value = maxRGB;
  801. return {h:hue,s:saturation,v:value};
  802. };
  803. /**
  804. * https://gist.github.com/mjijackson/5311256
  805. * @param hue
  806. * @param saturation
  807. * @param value
  808. * @returns {{r: number, g: number, b: number}}
  809. * @constructor
  810. */
  811. exports.HSVToRGB = function(h, s, v) {
  812. var r, g, b;
  813. var i = Math.floor(h * 6);
  814. var f = h * 6 - i;
  815. var p = v * (1 - s);
  816. var q = v * (1 - f * s);
  817. var t = v * (1 - (1 - f) * s);
  818. switch (i % 6) {
  819. case 0: r = v, g = t, b = p; break;
  820. case 1: r = q, g = v, b = p; break;
  821. case 2: r = p, g = v, b = t; break;
  822. case 3: r = p, g = q, b = v; break;
  823. case 4: r = t, g = p, b = v; break;
  824. case 5: r = v, g = p, b = q; break;
  825. }
  826. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  827. };
  828. exports.HSVToHex = function(h, s, v) {
  829. var rgb = exports.HSVToRGB(h, s, v);
  830. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  831. };
  832. exports.hexToHSV = function(hex) {
  833. var rgb = exports.hexToRGB(hex);
  834. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  835. };
  836. exports.isValidHex = function(hex) {
  837. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  838. return isOk;
  839. };
  840. /**
  841. * This recursively redirects the prototype of JSON objects to the referenceObject
  842. * This is used for default options.
  843. *
  844. * @param referenceObject
  845. * @returns {*}
  846. */
  847. exports.selectiveBridgeObject = function(fields, referenceObject) {
  848. if (typeof referenceObject == "object") {
  849. var objectTo = Object.create(referenceObject);
  850. for (var i = 0; i < fields.length; i++) {
  851. if (referenceObject.hasOwnProperty(fields[i])) {
  852. if (typeof referenceObject[fields[i]] == "object") {
  853. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  854. }
  855. }
  856. }
  857. return objectTo;
  858. }
  859. else {
  860. return null;
  861. }
  862. };
  863. /**
  864. * This recursively redirects the prototype of JSON objects to the referenceObject
  865. * This is used for default options.
  866. *
  867. * @param referenceObject
  868. * @returns {*}
  869. */
  870. exports.bridgeObject = function(referenceObject) {
  871. if (typeof referenceObject == "object") {
  872. var objectTo = Object.create(referenceObject);
  873. for (var i in referenceObject) {
  874. if (referenceObject.hasOwnProperty(i)) {
  875. if (typeof referenceObject[i] == "object") {
  876. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  877. }
  878. }
  879. }
  880. return objectTo;
  881. }
  882. else {
  883. return null;
  884. }
  885. };
  886. /**
  887. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  888. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  889. *
  890. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  891. * @param [object] options | options
  892. * @param [String] option | this is the option key in the options argument
  893. * @private
  894. */
  895. exports.mergeOptions = function (mergeTarget, options, option) {
  896. if (options[option] !== undefined) {
  897. if (typeof options[option] == 'boolean') {
  898. mergeTarget[option].enabled = options[option];
  899. }
  900. else {
  901. mergeTarget[option].enabled = true;
  902. for (prop in options[option]) {
  903. if (options[option].hasOwnProperty(prop)) {
  904. mergeTarget[option][prop] = options[option][prop];
  905. }
  906. }
  907. }
  908. }
  909. }
  910. /**
  911. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  912. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  913. *
  914. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  915. * @param [object] options | options
  916. * @param [String] option | this is the option key in the options argument
  917. * @private
  918. */
  919. exports.mergeOptions = function (mergeTarget, options, option) {
  920. if (options[option] !== undefined) {
  921. if (typeof options[option] == 'boolean') {
  922. mergeTarget[option].enabled = options[option];
  923. }
  924. else {
  925. mergeTarget[option].enabled = true;
  926. for (prop in options[option]) {
  927. if (options[option].hasOwnProperty(prop)) {
  928. mergeTarget[option][prop] = options[option][prop];
  929. }
  930. }
  931. }
  932. }
  933. }
  934. /**
  935. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  936. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  937. * 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
  938. * if the time we selected (start or end) is within the current range).
  939. *
  940. * 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
  941. * 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,
  942. * either the start OR end time has to be in the range.
  943. *
  944. * @param {Item[]} orderedItems Items ordered by start
  945. * @param {{start: number, end: number}} range
  946. * @param {String} field
  947. * @param {String} field2
  948. * @returns {number}
  949. * @private
  950. */
  951. exports.binarySearch = function(orderedItems, range, field, field2) {
  952. var array = orderedItems;
  953. var maxIterations = 10000;
  954. var iteration = 0;
  955. var found = false;
  956. var low = 0;
  957. var high = array.length;
  958. var newLow = low;
  959. var newHigh = high;
  960. var guess = Math.floor(0.5*(high+low));
  961. var value;
  962. if (high == 0) {
  963. guess = -1;
  964. }
  965. else if (high == 1) {
  966. if (array[guess].isVisible(range)) {
  967. guess = 0;
  968. }
  969. else {
  970. guess = -1;
  971. }
  972. }
  973. else {
  974. high -= 1;
  975. while (found == false && iteration < maxIterations) {
  976. value = field2 === undefined ? array[guess][field] : array[guess][field][field2];
  977. if (array[guess].isVisible(range)) {
  978. found = true;
  979. }
  980. else {
  981. if (value < range.start) { // it is too small --> increase low
  982. newLow = Math.floor(0.5*(high+low));
  983. }
  984. else { // it is too big --> decrease high
  985. newHigh = Math.floor(0.5*(high+low));
  986. }
  987. // not in list;
  988. if (low == newLow && high == newHigh) {
  989. guess = -1;
  990. found = true;
  991. }
  992. else {
  993. high = newHigh; low = newLow;
  994. guess = Math.floor(0.5*(high+low));
  995. }
  996. }
  997. iteration++;
  998. }
  999. if (iteration >= maxIterations) {
  1000. console.log("BinarySearch too many iterations. Aborting.");
  1001. }
  1002. }
  1003. return guess;
  1004. };
  1005. /**
  1006. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1007. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1008. * 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
  1009. * if the time we selected (start or end) is within the current range).
  1010. *
  1011. * 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
  1012. * 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,
  1013. * either the start OR end time has to be in the range.
  1014. *
  1015. * @param {Array} orderedItems
  1016. * @param {{start: number, end: number}} target
  1017. * @param {String} field
  1018. * @param {String} sidePreference 'before' or 'after'
  1019. * @returns {number}
  1020. * @private
  1021. */
  1022. exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
  1023. var maxIterations = 10000;
  1024. var iteration = 0;
  1025. var array = orderedItems;
  1026. var found = false;
  1027. var low = 0;
  1028. var high = array.length;
  1029. var newLow = low;
  1030. var newHigh = high;
  1031. var guess = Math.floor(0.5*(high+low));
  1032. var newGuess;
  1033. var prevValue, value, nextValue;
  1034. if (high == 0) {guess = -1;}
  1035. else if (high == 1) {
  1036. value = array[guess][field];
  1037. if (value == target) {
  1038. guess = 0;
  1039. }
  1040. else {
  1041. guess = -1;
  1042. }
  1043. }
  1044. else {
  1045. high -= 1;
  1046. while (found == false && iteration < maxIterations) {
  1047. prevValue = array[Math.max(0,guess - 1)][field];
  1048. value = array[guess][field];
  1049. nextValue = array[Math.min(array.length-1,guess + 1)][field];
  1050. if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
  1051. found = true;
  1052. if (value != target) {
  1053. if (sidePreference == 'before') {
  1054. if (prevValue < target && value > target) {
  1055. guess = Math.max(0,guess - 1);
  1056. }
  1057. }
  1058. else {
  1059. if (value < target && nextValue > target) {
  1060. guess = Math.min(array.length-1,guess + 1);
  1061. }
  1062. }
  1063. }
  1064. }
  1065. else {
  1066. if (value < target) { // it is too small --> increase low
  1067. newLow = Math.floor(0.5*(high+low));
  1068. }
  1069. else { // it is too big --> decrease high
  1070. newHigh = Math.floor(0.5*(high+low));
  1071. }
  1072. newGuess = Math.floor(0.5*(high+low));
  1073. // not in list;
  1074. if (low == newLow && high == newHigh) {
  1075. guess = -1;
  1076. found = true;
  1077. }
  1078. else {
  1079. high = newHigh; low = newLow;
  1080. guess = Math.floor(0.5*(high+low));
  1081. }
  1082. }
  1083. iteration++;
  1084. }
  1085. if (iteration >= maxIterations) {
  1086. console.log("BinarySearch too many iterations. Aborting.");
  1087. }
  1088. }
  1089. return guess;
  1090. };