justgage.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220
  1. /**
  2. * JustGage - animated gauges using RaphaelJS
  3. * Check http://www.justgage.com for official releases
  4. * Licensed under MIT.
  5. * @author Bojan Djuricic (@Toorshia)
  6. **/
  7. JustGage = function(config) {
  8. var obj = this;
  9. // Helps in case developer wants to debug it. unobtrusive
  10. if (config === null || config === undefined) {
  11. console.log('* justgage: Make sure to pass options to the constructor!');
  12. return false;
  13. }
  14. var node;
  15. if (config.id !== null && config.id !== undefined) {
  16. node = document.getElementById(config.id);
  17. if (!node) {
  18. console.log('* justgage: No element with id : %s found', config.id);
  19. return false;
  20. }
  21. } else if (config.parentNode !== null && config.parentNode !== undefined) {
  22. node = config.parentNode;
  23. } else {
  24. console.log('* justgage: Make sure to pass the existing element id or parentNode to the constructor.');
  25. return false;
  26. }
  27. var dataset = node.dataset ? node.dataset : {};
  28. // check for defaults
  29. var defaults = (config.defaults !== null && config.defaults !== undefined) ? config.defaults : false;
  30. if (defaults !== false) {
  31. config = extend({}, config, defaults);
  32. delete config.defaults;
  33. }
  34. // configurable parameters
  35. obj.config = {
  36. // id : string
  37. // this is container element id
  38. id: config.id,
  39. // value : float
  40. // value gauge is showing
  41. value: kvLookup('value', config, dataset, 0, 'float'),
  42. // defaults : bool
  43. // defaults parameter to use
  44. defaults: kvLookup('defaults', config, dataset, 0, false),
  45. // parentNode : node object
  46. // this is container element
  47. parentNode: kvLookup('parentNode', config, dataset, null),
  48. // width : int
  49. // gauge width
  50. width: kvLookup('width', config, dataset, null),
  51. // height : int
  52. // gauge height
  53. height: kvLookup('height', config, dataset, null),
  54. // title : string
  55. // gauge title
  56. title: kvLookup('title', config, dataset, ""),
  57. // titleFontColor : string
  58. // color of gauge title
  59. titleFontColor: kvLookup('titleFontColor', config, dataset, "#999999"),
  60. // titleFontFamily : string
  61. // color of gauge title
  62. titleFontFamily: kvLookup('titleFontFamily', config, dataset, "sans-serif"),
  63. // titlePosition : string
  64. // 'above' or 'below'
  65. titlePosition: kvLookup('titlePosition', config, dataset, "above"),
  66. // valueFontColor : string
  67. // color of label showing current value
  68. valueFontColor: kvLookup('valueFontColor', config, dataset, "#010101"),
  69. // valueFontFamily : string
  70. // color of label showing current value
  71. valueFontFamily: kvLookup('valueFontFamily', config, dataset, "Arial"),
  72. // symbol : string
  73. // special symbol to show next to value
  74. symbol: kvLookup('symbol', config, dataset, ''),
  75. // min : float
  76. // min value
  77. min: kvLookup('min', config, dataset, 0, 'float'),
  78. // max : float
  79. // max value
  80. max: kvLookup('max', config, dataset, 100, 'float'),
  81. // reverse : bool
  82. // reverse min and max
  83. reverse: kvLookup('reverse', config, dataset, false),
  84. // humanFriendlyDecimal : int
  85. // number of decimal places for our human friendly number to contain
  86. humanFriendlyDecimal: kvLookup('humanFriendlyDecimal', config, dataset, 0),
  87. // textRenderer: func
  88. // function applied before rendering text
  89. textRenderer: kvLookup('textRenderer', config, dataset, null),
  90. // gaugeWidthScale : float
  91. // width of the gauge element
  92. gaugeWidthScale: kvLookup('gaugeWidthScale', config, dataset, 1.0),
  93. // gaugeColor : string
  94. // background color of gauge element
  95. gaugeColor: kvLookup('gaugeColor', config, dataset, "#edebeb"),
  96. // label : string
  97. // text to show below value
  98. label: kvLookup('label', config, dataset, ''),
  99. // labelFontColor : string
  100. // color of label showing label under value
  101. labelFontColor: kvLookup('labelFontColor', config, dataset, "#b3b3b3"),
  102. // shadowOpacity : int
  103. // 0 ~ 1
  104. shadowOpacity: kvLookup('shadowOpacity', config, dataset, 0.2),
  105. // shadowSize: int
  106. // inner shadow size
  107. shadowSize: kvLookup('shadowSize', config, dataset, 5),
  108. // shadowVerticalOffset : int
  109. // how much shadow is offset from top
  110. shadowVerticalOffset: kvLookup('shadowVerticalOffset', config, dataset, 3),
  111. // levelColors : string[]
  112. // colors of indicator, from lower to upper, in RGB format
  113. levelColors: kvLookup('levelColors', config, dataset, ["#a9d70b", "#f9c802", "#ff0000"], 'array', ','),
  114. // startAnimationTime : int
  115. // length of initial animation
  116. startAnimationTime: kvLookup('startAnimationTime', config, dataset, 700),
  117. // startAnimationType : string
  118. // type of initial animation (linear, >, <, <>, bounce)
  119. startAnimationType: kvLookup('startAnimationType', config, dataset, '>'),
  120. // refreshAnimationTime : int
  121. // length of refresh animation
  122. refreshAnimationTime: kvLookup('refreshAnimationTime', config, dataset, 700),
  123. // refreshAnimationType : string
  124. // type of refresh animation (linear, >, <, <>, bounce)
  125. refreshAnimationType: kvLookup('refreshAnimationType', config, dataset, '>'),
  126. // donutStartAngle : int
  127. // angle to start from when in donut mode
  128. donutStartAngle: kvLookup('donutStartAngle', config, dataset, 90),
  129. // valueMinFontSize : int
  130. // absolute minimum font size for the value
  131. valueMinFontSize: kvLookup('valueMinFontSize', config, dataset, 16),
  132. // titleMinFontSize
  133. // absolute minimum font size for the title
  134. titleMinFontSize: kvLookup('titleMinFontSize', config, dataset, 10),
  135. // labelMinFontSize
  136. // absolute minimum font size for the label
  137. labelMinFontSize: kvLookup('labelMinFontSize', config, dataset, 10),
  138. // minLabelMinFontSize
  139. // absolute minimum font size for the minimum label
  140. minLabelMinFontSize: kvLookup('minLabelMinFontSize', config, dataset, 10),
  141. // maxLabelMinFontSize
  142. // absolute minimum font size for the maximum label
  143. maxLabelMinFontSize: kvLookup('maxLabelMinFontSize', config, dataset, 10),
  144. // hideValue : bool
  145. // hide value text
  146. hideValue: kvLookup('hideValue', config, dataset, false),
  147. // hideMinMax : bool
  148. // hide min and max values
  149. hideMinMax: kvLookup('hideMinMax', config, dataset, false),
  150. // hideInnerShadow : bool
  151. // hide inner shadow
  152. hideInnerShadow: kvLookup('hideInnerShadow', config, dataset, false),
  153. // humanFriendly : bool
  154. // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
  155. humanFriendly: kvLookup('humanFriendly', config, dataset, false),
  156. // noGradient : bool
  157. // whether to use gradual color change for value, or sector-based
  158. noGradient: kvLookup('noGradient', config, dataset, false),
  159. // donut : bool
  160. // show full donut gauge
  161. donut: kvLookup('donut', config, dataset, false),
  162. // relativeGaugeSize : bool
  163. // whether gauge size should follow changes in container element size
  164. relativeGaugeSize: kvLookup('relativeGaugeSize', config, dataset, false),
  165. // counter : bool
  166. // animate level number change
  167. counter: kvLookup('counter', config, dataset, false),
  168. // decimals : int
  169. // number of digits after floating point
  170. decimals: kvLookup('decimals', config, dataset, 0),
  171. // customSectors : [] of objects
  172. // number of digits after floating point
  173. customSectors: kvLookup('customSectors', config, dataset, []),
  174. // formatNumber: boolean
  175. // formats numbers with commas where appropriate
  176. formatNumber: kvLookup('formatNumber', config, dataset, false),
  177. // pointer : bool
  178. // show value pointer
  179. pointer: kvLookup('pointer', config, dataset, false),
  180. // pointerOptions : object
  181. // define pointer look
  182. pointerOptions: kvLookup('pointerOptions', config, dataset, [])
  183. };
  184. // variables
  185. var
  186. canvasW,
  187. canvasH,
  188. widgetW,
  189. widgetH,
  190. aspect,
  191. dx,
  192. dy,
  193. titleFontSize,
  194. titleX,
  195. titleY,
  196. valueFontSize,
  197. valueX,
  198. valueY,
  199. labelFontSize,
  200. labelX,
  201. labelY,
  202. minFontSize,
  203. minX,
  204. minY,
  205. maxFontSize,
  206. maxX,
  207. maxY;
  208. // overflow values
  209. if (obj.config.value > obj.config.max) obj.config.value = obj.config.max;
  210. if (obj.config.value < obj.config.min) obj.config.value = obj.config.min;
  211. obj.originalValue = kvLookup('value', config, dataset, -1, 'float');
  212. // create canvas
  213. if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) {
  214. obj.canvas = Raphael(obj.config.id, "100%", "100%");
  215. } else if (obj.config.parentNode !== null) {
  216. obj.canvas = Raphael(obj.config.parentNode, "100%", "100%");
  217. }
  218. if (obj.config.relativeGaugeSize === true) {
  219. obj.canvas.setViewBox(0, 0, 200, 150, true);
  220. }
  221. // canvas dimensions
  222. if (obj.config.relativeGaugeSize === true) {
  223. canvasW = 200;
  224. canvasH = 150;
  225. } else if (obj.config.width !== null && obj.config.height !== null) {
  226. canvasW = obj.config.width;
  227. canvasH = obj.config.height;
  228. } else if (obj.config.parentNode !== null) {
  229. obj.canvas.setViewBox(0, 0, 200, 150, true);
  230. canvasW = 200;
  231. canvasH = 150;
  232. } else {
  233. canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1;
  234. canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1;
  235. }
  236. // widget dimensions
  237. if (obj.config.donut === true) {
  238. // DONUT *******************************
  239. // width more than height
  240. if (canvasW > canvasH) {
  241. widgetH = canvasH;
  242. widgetW = widgetH;
  243. // width less than height
  244. } else if (canvasW < canvasH) {
  245. widgetW = canvasW;
  246. widgetH = widgetW;
  247. // if height don't fit, rescale both
  248. if (widgetH > canvasH) {
  249. aspect = widgetH / canvasH;
  250. widgetH = widgetH / aspect;
  251. widgetW = widgetH / aspect;
  252. }
  253. // equal
  254. } else {
  255. widgetW = canvasW;
  256. widgetH = widgetW;
  257. }
  258. // delta
  259. dx = (canvasW - widgetW) / 2;
  260. dy = (canvasH - widgetH) / 2;
  261. // title
  262. titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
  263. titleX = dx + widgetW / 2;
  264. titleY = dy + widgetH / 11;
  265. // value
  266. valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
  267. valueX = dx + widgetW / 2;
  268. if (obj.config.label !== '') {
  269. valueY = dy + widgetH / 1.85;
  270. } else {
  271. valueY = dy + widgetH / 1.7;
  272. }
  273. // label
  274. labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  275. labelX = dx + widgetW / 2;
  276. labelY = valueY + labelFontSize;
  277. // min
  278. minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  279. minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
  280. minY = labelY;
  281. // max
  282. maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
  283. maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
  284. maxY = labelY;
  285. } else {
  286. // HALF *******************************
  287. // width more than height
  288. if (canvasW > canvasH) {
  289. widgetH = canvasH;
  290. widgetW = widgetH * 1.25;
  291. //if width doesn't fit, rescale both
  292. if (widgetW > canvasW) {
  293. aspect = widgetW / canvasW;
  294. widgetW = widgetW / aspect;
  295. widgetH = widgetH / aspect;
  296. }
  297. // width less than height
  298. } else if (canvasW < canvasH) {
  299. widgetW = canvasW;
  300. widgetH = widgetW / 1.25;
  301. // if height don't fit, rescale both
  302. if (widgetH > canvasH) {
  303. aspect = widgetH / canvasH;
  304. widgetH = widgetH / aspect;
  305. widgetW = widgetH / aspect;
  306. }
  307. // equal
  308. } else {
  309. widgetW = canvasW;
  310. widgetH = widgetW * 0.75;
  311. }
  312. // delta
  313. dx = (canvasW - widgetW) / 2;
  314. dy = (canvasH - widgetH) / 2;
  315. if (obj.config.titlePosition === 'below') {
  316. // shift whole thing down
  317. dy -= (widgetH / 6.4);
  318. }
  319. // title
  320. titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize;
  321. titleX = dx + widgetW / 2;
  322. titleY = dy + (obj.config.titlePosition === 'below' ? (widgetH * 1.07) : (widgetH / 6.4));
  323. // value
  324. valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize;
  325. valueX = dx + widgetW / 2;
  326. valueY = dy + widgetH / 1.275;
  327. // label
  328. labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize;
  329. labelX = dx + widgetW / 2;
  330. labelY = valueY + valueFontSize / 2 + 5;
  331. // min
  332. minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize;
  333. minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
  334. minY = labelY;
  335. // max
  336. maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize;
  337. maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
  338. maxY = labelY;
  339. }
  340. // parameters
  341. obj.params = {
  342. canvasW: canvasW,
  343. canvasH: canvasH,
  344. widgetW: widgetW,
  345. widgetH: widgetH,
  346. dx: dx,
  347. dy: dy,
  348. titleFontSize: titleFontSize,
  349. titleX: titleX,
  350. titleY: titleY,
  351. valueFontSize: valueFontSize,
  352. valueX: valueX,
  353. valueY: valueY,
  354. labelFontSize: labelFontSize,
  355. labelX: labelX,
  356. labelY: labelY,
  357. minFontSize: minFontSize,
  358. minX: minX,
  359. minY: minY,
  360. maxFontSize: maxFontSize,
  361. maxX: maxX,
  362. maxY: maxY
  363. };
  364. // var clear
  365. canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null;
  366. // pki - custom attribute for generating gauge paths
  367. obj.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) {
  368. var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
  369. if (donut) {
  370. alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
  371. Ro = w / 2 - w / 7;
  372. Ri = Ro - w / 6.666666666666667 * gws;
  373. Cx = w / 2 + dx;
  374. Cy = h / 1.95 + dy;
  375. Xo = w / 2 + dx + Ro * Math.cos(alpha);
  376. Yo = h - (h - Cy) - Ro * Math.sin(alpha);
  377. Xi = w / 2 + dx + Ri * Math.cos(alpha);
  378. Yi = h - (h - Cy) - Ri * Math.sin(alpha);
  379. path = "M" + (Cx - Ri) + "," + Cy + " ";
  380. path += "L" + (Cx - Ro) + "," + Cy + " ";
  381. if (value > ((max - min) / 2)) {
  382. path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
  383. }
  384. path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
  385. path += "L" + Xi + "," + Yi + " ";
  386. if (value > ((max - min) / 2)) {
  387. path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
  388. }
  389. path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
  390. path += "Z ";
  391. return {
  392. path: path
  393. };
  394. } else {
  395. alpha = (1 - (value - min) / (max - min)) * Math.PI;
  396. Ro = w / 2 - w / 10;
  397. Ri = Ro - w / 6.666666666666667 * gws;
  398. Cx = w / 2 + dx;
  399. Cy = h / 1.25 + dy;
  400. Xo = w / 2 + dx + Ro * Math.cos(alpha);
  401. Yo = h - (h - Cy) - Ro * Math.sin(alpha);
  402. Xi = w / 2 + dx + Ri * Math.cos(alpha);
  403. Yi = h - (h - Cy) - Ri * Math.sin(alpha);
  404. path = "M" + (Cx - Ri) + "," + Cy + " ";
  405. path += "L" + (Cx - Ro) + "," + Cy + " ";
  406. path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
  407. path += "L" + Xi + "," + Yi + " ";
  408. path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
  409. path += "Z ";
  410. return {
  411. path: path
  412. };
  413. }
  414. // var clear
  415. alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
  416. };
  417. // ndl - custom attribute for generating needle path
  418. obj.canvas.customAttributes.ndl = function(value, min, max, w, h, dx, dy, gws, donut) {
  419. var dlt = w * 3.5 / 100;
  420. var dlb = w / 15;
  421. var dw = w / 100;
  422. if (obj.config.pointerOptions.toplength != null && obj.config.pointerOptions.toplength != undefined) dlt = obj.config.pointerOptions.toplength;
  423. if (obj.config.pointerOptions.bottomlength != null && obj.config.pointerOptions.bottomlength != undefined) dlb = obj.config.pointerOptions.bottomlength;
  424. if (obj.config.pointerOptions.bottomwidth != null && obj.config.pointerOptions.bottomwidth != undefined) dw = obj.config.pointerOptions.bottomwidth;
  425. var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path;
  426. if (donut) {
  427. alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
  428. Ro = w / 2 - w / 7;
  429. Ri = Ro - w / 6.666666666666667 * gws;
  430. Cx = w / 2 + dx;
  431. Cy = h / 1.95 + dy;
  432. Xo = w / 2 + dx + Ro * Math.cos(alpha);
  433. Yo = h - (h - Cy) - Ro * Math.sin(alpha);
  434. Xi = w / 2 + dx + Ri * Math.cos(alpha);
  435. Yi = h - (h - Cy) - Ri * Math.sin(alpha);
  436. Xc = Xo + dlt * Math.cos(alpha);
  437. Yc = Yo - dlt * Math.sin(alpha);
  438. Xz = Xi - dlb * Math.cos(alpha);
  439. Yz = Yi + dlb * Math.sin(alpha);
  440. Xa = Xz + dw * Math.sin(alpha);
  441. Ya = Yz + dw * Math.cos(alpha);
  442. Xb = Xz - dw * Math.sin(alpha);
  443. Yb = Yz - dw * Math.cos(alpha);
  444. path = 'M' + Xa + ',' + Ya + ' ';
  445. path += 'L' + Xb + ',' + Yb + ' ';
  446. path += 'L' + Xc + ',' + Yc + ' ';
  447. path += 'Z ';
  448. return {
  449. path: path
  450. };
  451. } else {
  452. alpha = (1 - (value - min) / (max - min)) * Math.PI;
  453. Ro = w / 2 - w / 10;
  454. Ri = Ro - w / 6.666666666666667 * gws;
  455. Cx = w / 2 + dx;
  456. Cy = h / 1.25 + dy;
  457. Xo = w / 2 + dx + Ro * Math.cos(alpha);
  458. Yo = h - (h - Cy) - Ro * Math.sin(alpha);
  459. Xi = w / 2 + dx + Ri * Math.cos(alpha);
  460. Yi = h - (h - Cy) - Ri * Math.sin(alpha);
  461. Xc = Xo + dlt * Math.cos(alpha);
  462. Yc = Yo - dlt * Math.sin(alpha);
  463. Xz = Xi - dlb * Math.cos(alpha);
  464. Yz = Yi + dlb * Math.sin(alpha);
  465. Xa = Xz + dw * Math.sin(alpha);
  466. Ya = Yz + dw * Math.cos(alpha);
  467. Xb = Xz - dw * Math.sin(alpha);
  468. Yb = Yz - dw * Math.cos(alpha);
  469. path = 'M' + Xa + ',' + Ya + ' ';
  470. path += 'L' + Xb + ',' + Yb + ' ';
  471. path += 'L' + Xc + ',' + Yc + ' ';
  472. path += 'Z ';
  473. return {
  474. path: path
  475. };
  476. }
  477. // var clear
  478. alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path = null;
  479. };
  480. // gauge
  481. obj.gauge = obj.canvas.path().attr({
  482. "stroke": "none",
  483. "fill": obj.config.gaugeColor,
  484. pki: [
  485. obj.config.max,
  486. obj.config.min,
  487. obj.config.max,
  488. obj.params.widgetW,
  489. obj.params.widgetH,
  490. obj.params.dx,
  491. obj.params.dy,
  492. obj.config.gaugeWidthScale,
  493. obj.config.donut,
  494. obj.config.reverse
  495. ]
  496. });
  497. // level
  498. obj.level = obj.canvas.path().attr({
  499. "stroke": "none",
  500. "fill": getColor(obj.config.value, (obj.config.value - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors),
  501. pki: [
  502. obj.config.min,
  503. obj.config.min,
  504. obj.config.max,
  505. obj.params.widgetW,
  506. obj.params.widgetH,
  507. obj.params.dx,
  508. obj.params.dy,
  509. obj.config.gaugeWidthScale,
  510. obj.config.donut,
  511. obj.config.reverse
  512. ]
  513. });
  514. if (obj.config.donut) {
  515. obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
  516. }
  517. if (obj.config.pointer) {
  518. // needle
  519. obj.needle = obj.canvas.path().attr({
  520. "stroke": (obj.config.pointerOptions.stroke !== null && obj.config.pointerOptions.stroke !== undefined) ? obj.config.pointerOptions.stroke : "none",
  521. "stroke-width": (obj.config.pointerOptions.stroke_width !== null && obj.config.pointerOptions.stroke_width !== undefined) ? obj.config.pointerOptions.stroke_width : 0,
  522. "stroke-linecap": (obj.config.pointerOptions.stroke_linecap !== null && obj.config.pointerOptions.stroke_linecap !== undefined) ? obj.config.pointerOptions.stroke_linecap : "square",
  523. "fill": (obj.config.pointerOptions.color !== null && obj.config.pointerOptions.color !== undefined) ? obj.config.pointerOptions.color : "#000000",
  524. ndl: [
  525. obj.config.min,
  526. obj.config.min,
  527. obj.config.max,
  528. obj.params.widgetW,
  529. obj.params.widgetH,
  530. obj.params.dx,
  531. obj.params.dy,
  532. obj.config.gaugeWidthScale,
  533. obj.config.donut
  534. ]
  535. });
  536. if (obj.config.donut) {
  537. obj.needle.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
  538. }
  539. }
  540. // title
  541. obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title);
  542. obj.txtTitle.attr({
  543. "font-size": obj.params.titleFontSize,
  544. "font-weight": "bold",
  545. "font-family": obj.config.titleFontFamily,
  546. "fill": obj.config.titleFontColor,
  547. "fill-opacity": "1"
  548. });
  549. setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY);
  550. // value
  551. obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0);
  552. obj.txtValue.attr({
  553. "font-size": obj.params.valueFontSize,
  554. "font-weight": "bold",
  555. "font-family": obj.config.valueFontFamily,
  556. "fill": obj.config.valueFontColor,
  557. "fill-opacity": "0"
  558. });
  559. setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
  560. // label
  561. obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label);
  562. obj.txtLabel.attr({
  563. "font-size": obj.params.labelFontSize,
  564. "font-weight": "normal",
  565. "font-family": "Arial",
  566. "fill": obj.config.labelFontColor,
  567. "fill-opacity": "0"
  568. });
  569. setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY);
  570. // min
  571. var min = obj.config.min;
  572. if (obj.config.reverse) {
  573. min = obj.config.max;
  574. }
  575. obj.txtMinimum = min;
  576. if (obj.config.humanFriendly) {
  577. obj.txtMinimum = humanFriendlyNumber(min, obj.config.humanFriendlyDecimal);
  578. } else if (obj.config.formatNumber) {
  579. obj.txtMinimum = formatNumber(min);
  580. }
  581. obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum);
  582. obj.txtMin.attr({
  583. "font-size": obj.params.minFontSize,
  584. "font-weight": "normal",
  585. "font-family": "Arial",
  586. "fill": obj.config.labelFontColor,
  587. "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
  588. });
  589. setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
  590. // max
  591. var max = obj.config.max;
  592. if (obj.config.reverse) {
  593. max = obj.config.min;
  594. }
  595. obj.txtMaximum = max;
  596. if (obj.config.humanFriendly) {
  597. obj.txtMaximum = humanFriendlyNumber(max, obj.config.humanFriendlyDecimal);
  598. } else if (obj.config.formatNumber) {
  599. obj.txtMaximum = formatNumber(max);
  600. }
  601. obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum);
  602. obj.txtMax.attr({
  603. "font-size": obj.params.maxFontSize,
  604. "font-weight": "normal",
  605. "font-family": "Arial",
  606. "fill": obj.config.labelFontColor,
  607. "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
  608. });
  609. setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
  610. var defs = obj.canvas.canvas.childNodes[1];
  611. var svg = "http://www.w3.org/2000/svg";
  612. if (ie !== 'undefined' && ie < 9) {
  613. // VML mode - no SVG & SVG filter support
  614. } else if (ie !== 'undefined') {
  615. onCreateElementNsReady(function() {
  616. obj.generateShadow(svg, defs);
  617. });
  618. } else {
  619. obj.generateShadow(svg, defs);
  620. }
  621. // var clear
  622. defs, svg = null;
  623. // set value to display
  624. if (obj.config.textRenderer) {
  625. obj.originalValue = obj.config.textRenderer(obj.originalValue);
  626. } else if (obj.config.humanFriendly) {
  627. obj.originalValue = humanFriendlyNumber(obj.originalValue, obj.config.humanFriendlyDecimal) + obj.config.symbol;
  628. } else if (obj.config.formatNumber) {
  629. obj.originalValue = formatNumber(obj.originalValue) + obj.config.symbol;
  630. } else {
  631. obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
  632. }
  633. if (obj.config.counter === true) {
  634. //on each animation frame
  635. eve.on("raphael.anim.frame." + (obj.level.id), function() {
  636. var currentValue = obj.level.attr("pki")[0];
  637. if (obj.config.reverse) {
  638. currentValue = (obj.config.max * 1) + (obj.config.min * 1) - (obj.level.attr("pki")[0] * 1);
  639. }
  640. if (obj.config.textRenderer) {
  641. obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue)));
  642. } else if (obj.config.humanFriendly) {
  643. obj.txtValue.attr("text", humanFriendlyNumber(Math.floor(currentValue), obj.config.humanFriendlyDecimal) + obj.config.symbol);
  644. } else if (obj.config.formatNumber) {
  645. obj.txtValue.attr("text", formatNumber(Math.floor(currentValue)) + obj.config.symbol);
  646. } else {
  647. obj.txtValue.attr("text", (currentValue * 1).toFixed(obj.config.decimals) + obj.config.symbol);
  648. }
  649. setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
  650. currentValue = null;
  651. });
  652. //on animation end
  653. eve.on("raphael.anim.finish." + (obj.level.id), function() {
  654. obj.txtValue.attr({
  655. "text": obj.originalValue
  656. });
  657. setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
  658. });
  659. } else {
  660. //on animation start
  661. eve.on("raphael.anim.start." + (obj.level.id), function() {
  662. obj.txtValue.attr({
  663. "text": obj.originalValue
  664. });
  665. setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
  666. });
  667. }
  668. // animate gauge level, value & label
  669. var rvl = obj.config.value;
  670. if (obj.config.reverse) {
  671. rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
  672. }
  673. obj.level.animate({
  674. pki: [
  675. rvl,
  676. obj.config.min,
  677. obj.config.max,
  678. obj.params.widgetW,
  679. obj.params.widgetH,
  680. obj.params.dx,
  681. obj.params.dy,
  682. obj.config.gaugeWidthScale,
  683. obj.config.donut,
  684. obj.config.reverse
  685. ]
  686. }, obj.config.startAnimationTime, obj.config.startAnimationType);
  687. if (obj.config.pointer) {
  688. obj.needle.animate({
  689. ndl: [
  690. rvl,
  691. obj.config.min,
  692. obj.config.max,
  693. obj.params.widgetW,
  694. obj.params.widgetH,
  695. obj.params.dx,
  696. obj.params.dy,
  697. obj.config.gaugeWidthScale,
  698. obj.config.donut
  699. ]
  700. }, obj.config.startAnimationTime, obj.config.startAnimationType);
  701. }
  702. obj.txtValue.animate({
  703. "fill-opacity": (obj.config.hideValue) ? "0" : "1"
  704. }, obj.config.startAnimationTime, obj.config.startAnimationType);
  705. obj.txtLabel.animate({
  706. "fill-opacity": "1"
  707. }, obj.config.startAnimationTime, obj.config.startAnimationType);
  708. };
  709. /** Refresh gauge level */
  710. JustGage.prototype.refresh = function(val, max) {
  711. var obj = this;
  712. var displayVal, color, max = max || null;
  713. // set new max
  714. if (max !== null) {
  715. obj.config.max = max;
  716. // TODO: update customSectors
  717. obj.txtMaximum = obj.config.max;
  718. if (obj.config.humanFriendly) {
  719. obj.txtMaximum = humanFriendlyNumber(obj.config.max, obj.config.humanFriendlyDecimal);
  720. } else if (obj.config.formatNumber) {
  721. obj.txtMaximum = formatNumber(obj.config.max);
  722. }
  723. if (!obj.config.reverse) {
  724. obj.txtMax.attr({
  725. "text": obj.txtMaximum
  726. });
  727. setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
  728. } else {
  729. obj.txtMin.attr({
  730. "text": obj.txtMaximum
  731. });
  732. setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
  733. }
  734. }
  735. // overflow values
  736. displayVal = val;
  737. if ((val * 1) > (obj.config.max * 1)) {
  738. val = (obj.config.max * 1);
  739. }
  740. if ((val * 1) < (obj.config.min * 1)) {
  741. val = (obj.config.min * 1);
  742. }
  743. color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors);
  744. if (obj.config.textRenderer) {
  745. displayVal = obj.config.textRenderer(displayVal);
  746. } else if (obj.config.humanFriendly) {
  747. displayVal = humanFriendlyNumber(displayVal, obj.config.humanFriendlyDecimal) + obj.config.symbol;
  748. } else if (obj.config.formatNumber) {
  749. displayVal = formatNumber((displayVal * 1).toFixed(obj.config.decimals)) + obj.config.symbol;
  750. } else {
  751. displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol;
  752. }
  753. obj.originalValue = displayVal;
  754. obj.config.value = val * 1;
  755. if (!obj.config.counter) {
  756. obj.txtValue.attr({
  757. "text": displayVal
  758. });
  759. setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
  760. }
  761. var rvl = obj.config.value;
  762. if (obj.config.reverse) {
  763. rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
  764. }
  765. obj.level.animate({
  766. pki: [
  767. rvl,
  768. obj.config.min,
  769. obj.config.max,
  770. obj.params.widgetW,
  771. obj.params.widgetH,
  772. obj.params.dx,
  773. obj.params.dy,
  774. obj.config.gaugeWidthScale,
  775. obj.config.donut,
  776. obj.config.reverse
  777. ],
  778. "fill": color
  779. }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
  780. if (obj.config.pointer) {
  781. obj.needle.animate({
  782. ndl: [
  783. rvl,
  784. obj.config.min,
  785. obj.config.max,
  786. obj.params.widgetW,
  787. obj.params.widgetH,
  788. obj.params.dx,
  789. obj.params.dy,
  790. obj.config.gaugeWidthScale,
  791. obj.config.donut
  792. ]
  793. }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
  794. }
  795. // var clear
  796. obj, displayVal, color, max = null;
  797. };
  798. /** Generate shadow */
  799. JustGage.prototype.generateShadow = function(svg, defs) {
  800. var obj = this;
  801. var sid = "inner-shadow-" + obj.config.id;
  802. var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
  803. // FILTER
  804. gaussFilter = document.createElementNS(svg, "filter");
  805. gaussFilter.setAttribute("id", sid);
  806. defs.appendChild(gaussFilter);
  807. // offset
  808. feOffset = document.createElementNS(svg, "feOffset");
  809. feOffset.setAttribute("dx", 0);
  810. feOffset.setAttribute("dy", obj.config.shadowVerticalOffset);
  811. gaussFilter.appendChild(feOffset);
  812. // blur
  813. feGaussianBlur = document.createElementNS(svg, "feGaussianBlur");
  814. feGaussianBlur.setAttribute("result", "offset-blur");
  815. feGaussianBlur.setAttribute("stdDeviation", obj.config.shadowSize);
  816. gaussFilter.appendChild(feGaussianBlur);
  817. // composite 1
  818. feComposite1 = document.createElementNS(svg, "feComposite");
  819. feComposite1.setAttribute("operator", "out");
  820. feComposite1.setAttribute("in", "SourceGraphic");
  821. feComposite1.setAttribute("in2", "offset-blur");
  822. feComposite1.setAttribute("result", "inverse");
  823. gaussFilter.appendChild(feComposite1);
  824. // flood
  825. feFlood = document.createElementNS(svg, "feFlood");
  826. feFlood.setAttribute("flood-color", "black");
  827. feFlood.setAttribute("flood-opacity", obj.config.shadowOpacity);
  828. feFlood.setAttribute("result", "color");
  829. gaussFilter.appendChild(feFlood);
  830. // composite 2
  831. feComposite2 = document.createElementNS(svg, "feComposite");
  832. feComposite2.setAttribute("operator", "in");
  833. feComposite2.setAttribute("in", "color");
  834. feComposite2.setAttribute("in2", "inverse");
  835. feComposite2.setAttribute("result", "shadow");
  836. gaussFilter.appendChild(feComposite2);
  837. // composite 3
  838. feComposite3 = document.createElementNS(svg, "feComposite");
  839. feComposite3.setAttribute("operator", "over");
  840. feComposite3.setAttribute("in", "shadow");
  841. feComposite3.setAttribute("in2", "SourceGraphic");
  842. gaussFilter.appendChild(feComposite3);
  843. // set shadow
  844. if (!obj.config.hideInnerShadow) {
  845. obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + sid + ")");
  846. obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + sid + ")");
  847. }
  848. // var clear
  849. gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
  850. };
  851. //
  852. // tiny helper function to lookup value of a key from two hash tables
  853. // if none found, return defaultvalue
  854. //
  855. // key: string
  856. // tablea: object
  857. // tableb: DOMStringMap|object
  858. // defval: string|integer|float|null
  859. // datatype: return datatype
  860. // delimiter: delimiter to be used in conjunction with datatype formatting
  861. //
  862. function kvLookup(key, tablea, tableb, defval, datatype, delimiter) {
  863. var val = defval;
  864. var canConvert = false;
  865. if (!(key === null || key === undefined)) {
  866. if (tableb !== null && tableb !== undefined && typeof tableb === "object" && key in tableb) {
  867. val = tableb[key];
  868. canConvert = true;
  869. } else if (tablea !== null && tablea !== undefined && typeof tablea === "object" && key in tablea) {
  870. val = tablea[key];
  871. canConvert = true;
  872. } else {
  873. val = defval;
  874. }
  875. if (canConvert === true) {
  876. if (datatype !== null && datatype !== undefined) {
  877. switch (datatype) {
  878. case 'int':
  879. val = parseInt(val, 10);
  880. break;
  881. case 'float':
  882. val = parseFloat(val);
  883. break;
  884. default:
  885. break;
  886. }
  887. }
  888. }
  889. }
  890. return val;
  891. };
  892. /** Get color for value */
  893. function getColor(val, pct, col, noGradient, custSec) {
  894. var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
  895. var noGradient = noGradient || custSec.length > 0;
  896. if (custSec.length > 0) {
  897. for (var i = 0; i < custSec.length; i++) {
  898. if (val > custSec[i].lo && val <= custSec[i].hi) {
  899. return custSec[i].color;
  900. }
  901. }
  902. }
  903. no = col.length;
  904. if (no === 1) return col[0];
  905. inc = (noGradient) ? (1 / no) : (1 / (no - 1));
  906. colors = [];
  907. for (i = 0; i < col.length; i++) {
  908. percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
  909. rval = parseInt((cutHex(col[i])).substring(0, 2), 16);
  910. gval = parseInt((cutHex(col[i])).substring(2, 4), 16);
  911. bval = parseInt((cutHex(col[i])).substring(4, 6), 16);
  912. colors[i] = {
  913. pct: percentage,
  914. color: {
  915. r: rval,
  916. g: gval,
  917. b: bval
  918. }
  919. };
  920. }
  921. if (pct === 0) {
  922. return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
  923. }
  924. for (var j = 0; j < colors.length; j++) {
  925. if (pct <= colors[j].pct) {
  926. if (noGradient) {
  927. return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
  928. } else {
  929. lower = colors[j - 1];
  930. upper = colors[j];
  931. range = upper.pct - lower.pct;
  932. rangePct = (pct - lower.pct) / range;
  933. pctLower = 1 - rangePct;
  934. pctUpper = rangePct;
  935. color = {
  936. r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
  937. g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
  938. b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
  939. };
  940. return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
  941. }
  942. }
  943. }
  944. }
  945. /** Fix Raphael display:none tspan dy attribute bug */
  946. function setDy(elem, fontSize, txtYpos) {
  947. if ((!ie || ie > 9) && elem.node.firstChild.attributes.dy) {
  948. elem.node.firstChild.attributes.dy.value = 0;
  949. }
  950. }
  951. /** Random integer */
  952. function getRandomInt(min, max) {
  953. return Math.floor(Math.random() * (max - min + 1)) + min;
  954. }
  955. /** Cut hex */
  956. function cutHex(str) {
  957. return (str.charAt(0) == "#") ? str.substring(1, 7) : str;
  958. }
  959. /** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
  960. function humanFriendlyNumber(n, d) {
  961. var p, d2, i, s;
  962. p = Math.pow;
  963. d2 = p(10, d);
  964. i = 7;
  965. while (i) {
  966. s = p(10, i-- * 3);
  967. if (s <= n) {
  968. n = Math.round(n * d2 / s) / d2 + "KMGTPE" [i];
  969. }
  970. }
  971. return n;
  972. }
  973. /** Format numbers with commas - From: http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript */
  974. function formatNumber(x) {
  975. var parts = x.toString().split(".");
  976. parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  977. return parts.join(".");
  978. }
  979. /** Get style */
  980. function getStyle(oElm, strCssRule) {
  981. var strValue = "";
  982. if (document.defaultView && document.defaultView.getComputedStyle) {
  983. strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
  984. } else if (oElm.currentStyle) {
  985. strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) {
  986. return p1.toUpperCase();
  987. });
  988. strValue = oElm.currentStyle[strCssRule];
  989. }
  990. return strValue;
  991. }
  992. /** Create Element NS Ready */
  993. function onCreateElementNsReady(func) {
  994. if (document.createElementNS !== undefined) {
  995. func();
  996. } else {
  997. setTimeout(function() {
  998. onCreateElementNsReady(func);
  999. }, 100);
  1000. }
  1001. }
  1002. /** Get IE version */
  1003. // ----------------------------------------------------------
  1004. // A short snippet for detecting versions of IE in JavaScript
  1005. // without resorting to user-agent sniffing
  1006. // ----------------------------------------------------------
  1007. // If you're not in IE (or IE version is less than 5) then:
  1008. // ie === undefined
  1009. // If you're in IE (>=5) then you can determine which version:
  1010. // ie === 7; // IE7
  1011. // Thus, to detect IE:
  1012. // if (ie) {}
  1013. // And to detect the version:
  1014. // ie === 6 // IE6
  1015. // ie > 7 // IE8, IE9 ...
  1016. // ie < 9 // Anything less than IE9
  1017. // ----------------------------------------------------------
  1018. // UPDATE: Now using Live NodeList idea from @jdalton
  1019. var ie = (function() {
  1020. var undef,
  1021. v = 3,
  1022. div = document.createElement('div'),
  1023. all = div.getElementsByTagName('i');
  1024. while (
  1025. div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
  1026. all[0]
  1027. );
  1028. return v > 4 ? v : undef;
  1029. }());
  1030. // extend target object with second object
  1031. function extend(out) {
  1032. out = out || {};
  1033. for (var i = 1; i < arguments.length; i++) {
  1034. if (!arguments[i])
  1035. continue;
  1036. for (var key in arguments[i]) {
  1037. if (arguments[i].hasOwnProperty(key))
  1038. out[key] = arguments[i][key];
  1039. }
  1040. }
  1041. return out;
  1042. };