···11+<!DOCTYPE html>
22+<html lang="en">
33+44+<head>
55+ <meta name="description" content="A site with statistics regarding the decentralization status of various web services" />
66+ <meta charset="utf-8">
77+ <title>Are We Decentralized Yet?</title>
88+ <meta name="viewport" content="width=device-width, initial-scale=1">
99+ <meta name="author" content="Robert Ricci">
1010+1111+ <script type="text/javascript" src="justgage-1.2.2/raphael-2.1.4.min.js""></script>
1212+ <script type="text/javascript" src="justgage-1.2.2/justgage.js"></script>
1313+1414+ <script type="text/javascript" src="data.js"></script>
1515+1616+ <link rel="stylesheet" href="css/index.css">
1717+1818+</head>
1919+2020+<body>
2121+2222+ <h1>Are We Distributed Yet?</h1>
2323+2424+ <div class="container">
2525+ <h3>How Concentrated Are The:</h3>
2626+ <div class="rating">
2727+ <div id="gageFedi" class="gage"></div>
2828+ </div>
2929+ <div class="rating">
3030+ <div id="gageAtmosphere" class="gage"></div>
3131+ </div>
3232+ <h4>Data last updated: <span id="lastUpdate"></span></h4>
3333+ </div>
3434+3535+ <div class="container">
3636+ <p>
3737+ This page measures the concentration of the <a
3838+ href="https://fediverse.party/">Fediverse</a> and the <a
3939+ href="https://atproto.com/">Atmosphere</a> according to the
4040+ <a href="https://en.wikipedia.org/wiki/Herfindahl%E2%80%93Hirschman_index">Herfindahl–Hirschman
4141+ Index</a>,
4242+ an indicator from economics used to measure competition between firms in
4343+ an industry.
4444+ </p>
4545+4646+ <p>
4747+ A value of
4848+ <math><mfrac><mn>1</mn><mi>N</mi></mfrac><mo>*</mo></mo><mn>10000</mn></math>
4949+ (where <math><mi>N</mi></math> is the number of
5050+ firms, or in this case, servers) indicates perfect competition (all
5151+ servers are of equal size), while a value of 10000 indicates a monopoly
5252+ (there is only one server). In economics, values below 100 are considered
5353+ "Highly Competitive", below 1500 is "Unconcentrated", and above 2500 is
5454+ considered "Highly Concentrated".
5555+ </p>
5656+5757+ <p>
5858+ This currently measures the concentration of user data: in the
5959+ Fediverse, these are <a href="https://w3c.github.io/activitypub/#server-to-server-interactions">servers</a> (also known as instances);
6060+ in the Atmosphere, they are the
6161+ <a
6262+ href="https://atproto.com/guides/glossary#pds-personal-data-server">PDSes</a>
6363+ that host users' <a
6464+ href="https://atproto.com/guides/glossary#data-repo">data repos</a>. Other metrics and
6565+ comparisons are possible, and I'm interested in exploring them.
6666+ </p>
6767+6868+ <p>
6969+ Code and data are available <a href="https://github.com/ricci/distributed-social-networks/">on
7070+ GitHub</a>.
7171+ Comments and pull requests, including other metrics for measuring
7272+ distribution and resiliency, are welcome!
7373+ </p>
7474+7575+ <p>
7676+ By Rob Ricci: <a href="https://discuss.systems/@ricci">@ricci@discuss.systems</a> /
7777+ <a href="https://bsky.app/profile/ricci.io">@ricci.io</a>
7878+ </p>
7979+8080+8181+ </div>
8282+8383+8484+ <script>
8585+ var defaults = {
8686+ min: 0,
8787+ max: 10000,
8888+ titleFontFamily: "Kade-Regular",
8989+ valueFontFamily: "Kade-Regular",
9090+ valueFontColor: "white",
9191+ label: "HHI",
9292+ hideMinMax: "true",
9393+ pointer: "true",
9494+ pointerOptions: {
9595+ toplength: -10,
9696+ bottomlength: 5,
9797+ bottomwidth: 6,
9898+ color: '#8e8e93',
9999+ stroke: '#ffffff',
100100+ stroke_width: 2,
101101+ stroke_linecap: 'round'
102102+ },
103103+ };
104104+105105+ var hhi_values = [
106106+107107+ ];
108108+109109+ var gFedi = new JustGage({
110110+ id: "gageFedi",
111111+ title: "Fediverse",
112112+ value: data["fedi"]["HHI"],
113113+ defaults
114114+ });
115115+ var gAtmpsphere = new JustGage({
116116+ id: "gageAtmosphere",
117117+ value: data["at"]["HHI"],
118118+ title: "Atmosphere",
119119+ defaults
120120+ });
121121+122122+ document.getElementById("lastUpdate").textContent = data.lastUpdate;
123123+ </script>
124124+125125+</body>
126126+</html>
+1220
www/justgage-1.2.2/justgage.js
···11+/**
22+ * JustGage - animated gauges using RaphaelJS
33+ * Check http://www.justgage.com for official releases
44+ * Licensed under MIT.
55+ * @author Bojan Djuricic (@Toorshia)
66+ **/
77+88+JustGage = function(config) {
99+1010+ var obj = this;
1111+1212+ // Helps in case developer wants to debug it. unobtrusive
1313+ if (config === null || config === undefined) {
1414+ console.log('* justgage: Make sure to pass options to the constructor!');
1515+ return false;
1616+ }
1717+1818+ var node;
1919+2020+ if (config.id !== null && config.id !== undefined) {
2121+ node = document.getElementById(config.id);
2222+ if (!node) {
2323+ console.log('* justgage: No element with id : %s found', config.id);
2424+ return false;
2525+ }
2626+ } else if (config.parentNode !== null && config.parentNode !== undefined) {
2727+ node = config.parentNode;
2828+ } else {
2929+ console.log('* justgage: Make sure to pass the existing element id or parentNode to the constructor.');
3030+ return false;
3131+ }
3232+3333+ var dataset = node.dataset ? node.dataset : {};
3434+3535+ // check for defaults
3636+ var defaults = (config.defaults !== null && config.defaults !== undefined) ? config.defaults : false;
3737+ if (defaults !== false) {
3838+ config = extend({}, config, defaults);
3939+ delete config.defaults;
4040+ }
4141+4242+ // configurable parameters
4343+ obj.config = {
4444+ // id : string
4545+ // this is container element id
4646+ id: config.id,
4747+4848+ // value : float
4949+ // value gauge is showing
5050+ value: kvLookup('value', config, dataset, 0, 'float'),
5151+5252+ // defaults : bool
5353+ // defaults parameter to use
5454+ defaults: kvLookup('defaults', config, dataset, 0, false),
5555+5656+ // parentNode : node object
5757+ // this is container element
5858+ parentNode: kvLookup('parentNode', config, dataset, null),
5959+6060+ // width : int
6161+ // gauge width
6262+ width: kvLookup('width', config, dataset, null),
6363+6464+ // height : int
6565+ // gauge height
6666+ height: kvLookup('height', config, dataset, null),
6767+6868+ // title : string
6969+ // gauge title
7070+ title: kvLookup('title', config, dataset, ""),
7171+7272+ // titleFontColor : string
7373+ // color of gauge title
7474+ titleFontColor: kvLookup('titleFontColor', config, dataset, "#999999"),
7575+7676+ // titleFontFamily : string
7777+ // color of gauge title
7878+ titleFontFamily: kvLookup('titleFontFamily', config, dataset, "sans-serif"),
7979+8080+ // titlePosition : string
8181+ // 'above' or 'below'
8282+ titlePosition: kvLookup('titlePosition', config, dataset, "above"),
8383+8484+ // valueFontColor : string
8585+ // color of label showing current value
8686+ valueFontColor: kvLookup('valueFontColor', config, dataset, "#010101"),
8787+8888+ // valueFontFamily : string
8989+ // color of label showing current value
9090+ valueFontFamily: kvLookup('valueFontFamily', config, dataset, "Arial"),
9191+9292+ // symbol : string
9393+ // special symbol to show next to value
9494+ symbol: kvLookup('symbol', config, dataset, ''),
9595+9696+ // min : float
9797+ // min value
9898+ min: kvLookup('min', config, dataset, 0, 'float'),
9999+100100+ // max : float
101101+ // max value
102102+ max: kvLookup('max', config, dataset, 100, 'float'),
103103+104104+ // reverse : bool
105105+ // reverse min and max
106106+ reverse: kvLookup('reverse', config, dataset, false),
107107+108108+ // humanFriendlyDecimal : int
109109+ // number of decimal places for our human friendly number to contain
110110+ humanFriendlyDecimal: kvLookup('humanFriendlyDecimal', config, dataset, 0),
111111+112112+113113+ // textRenderer: func
114114+ // function applied before rendering text
115115+ textRenderer: kvLookup('textRenderer', config, dataset, null),
116116+117117+ // gaugeWidthScale : float
118118+ // width of the gauge element
119119+ gaugeWidthScale: kvLookup('gaugeWidthScale', config, dataset, 1.0),
120120+121121+ // gaugeColor : string
122122+ // background color of gauge element
123123+ gaugeColor: kvLookup('gaugeColor', config, dataset, "#edebeb"),
124124+125125+ // label : string
126126+ // text to show below value
127127+ label: kvLookup('label', config, dataset, ''),
128128+129129+ // labelFontColor : string
130130+ // color of label showing label under value
131131+ labelFontColor: kvLookup('labelFontColor', config, dataset, "#b3b3b3"),
132132+133133+ // shadowOpacity : int
134134+ // 0 ~ 1
135135+ shadowOpacity: kvLookup('shadowOpacity', config, dataset, 0.2),
136136+137137+ // shadowSize: int
138138+ // inner shadow size
139139+ shadowSize: kvLookup('shadowSize', config, dataset, 5),
140140+141141+ // shadowVerticalOffset : int
142142+ // how much shadow is offset from top
143143+ shadowVerticalOffset: kvLookup('shadowVerticalOffset', config, dataset, 3),
144144+145145+ // levelColors : string[]
146146+ // colors of indicator, from lower to upper, in RGB format
147147+ levelColors: kvLookup('levelColors', config, dataset, ["#a9d70b", "#f9c802", "#ff0000"], 'array', ','),
148148+149149+ // startAnimationTime : int
150150+ // length of initial animation
151151+ startAnimationTime: kvLookup('startAnimationTime', config, dataset, 700),
152152+153153+ // startAnimationType : string
154154+ // type of initial animation (linear, >, <, <>, bounce)
155155+ startAnimationType: kvLookup('startAnimationType', config, dataset, '>'),
156156+157157+ // refreshAnimationTime : int
158158+ // length of refresh animation
159159+ refreshAnimationTime: kvLookup('refreshAnimationTime', config, dataset, 700),
160160+161161+ // refreshAnimationType : string
162162+ // type of refresh animation (linear, >, <, <>, bounce)
163163+ refreshAnimationType: kvLookup('refreshAnimationType', config, dataset, '>'),
164164+165165+ // donutStartAngle : int
166166+ // angle to start from when in donut mode
167167+ donutStartAngle: kvLookup('donutStartAngle', config, dataset, 90),
168168+169169+ // valueMinFontSize : int
170170+ // absolute minimum font size for the value
171171+ valueMinFontSize: kvLookup('valueMinFontSize', config, dataset, 16),
172172+173173+ // titleMinFontSize
174174+ // absolute minimum font size for the title
175175+ titleMinFontSize: kvLookup('titleMinFontSize', config, dataset, 10),
176176+177177+ // labelMinFontSize
178178+ // absolute minimum font size for the label
179179+ labelMinFontSize: kvLookup('labelMinFontSize', config, dataset, 10),
180180+181181+ // minLabelMinFontSize
182182+ // absolute minimum font size for the minimum label
183183+ minLabelMinFontSize: kvLookup('minLabelMinFontSize', config, dataset, 10),
184184+185185+ // maxLabelMinFontSize
186186+ // absolute minimum font size for the maximum label
187187+ maxLabelMinFontSize: kvLookup('maxLabelMinFontSize', config, dataset, 10),
188188+189189+ // hideValue : bool
190190+ // hide value text
191191+ hideValue: kvLookup('hideValue', config, dataset, false),
192192+193193+ // hideMinMax : bool
194194+ // hide min and max values
195195+ hideMinMax: kvLookup('hideMinMax', config, dataset, false),
196196+197197+ // hideInnerShadow : bool
198198+ // hide inner shadow
199199+ hideInnerShadow: kvLookup('hideInnerShadow', config, dataset, false),
200200+201201+ // humanFriendly : bool
202202+ // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M)
203203+ humanFriendly: kvLookup('humanFriendly', config, dataset, false),
204204+205205+ // noGradient : bool
206206+ // whether to use gradual color change for value, or sector-based
207207+ noGradient: kvLookup('noGradient', config, dataset, false),
208208+209209+ // donut : bool
210210+ // show full donut gauge
211211+ donut: kvLookup('donut', config, dataset, false),
212212+213213+ // relativeGaugeSize : bool
214214+ // whether gauge size should follow changes in container element size
215215+ relativeGaugeSize: kvLookup('relativeGaugeSize', config, dataset, false),
216216+217217+ // counter : bool
218218+ // animate level number change
219219+ counter: kvLookup('counter', config, dataset, false),
220220+221221+ // decimals : int
222222+ // number of digits after floating point
223223+ decimals: kvLookup('decimals', config, dataset, 0),
224224+225225+ // customSectors : [] of objects
226226+ // number of digits after floating point
227227+ customSectors: kvLookup('customSectors', config, dataset, []),
228228+229229+ // formatNumber: boolean
230230+ // formats numbers with commas where appropriate
231231+ formatNumber: kvLookup('formatNumber', config, dataset, false),
232232+233233+ // pointer : bool
234234+ // show value pointer
235235+ pointer: kvLookup('pointer', config, dataset, false),
236236+237237+ // pointerOptions : object
238238+ // define pointer look
239239+ pointerOptions: kvLookup('pointerOptions', config, dataset, [])
240240+ };
241241+242242+ // variables
243243+ var
244244+ canvasW,
245245+ canvasH,
246246+ widgetW,
247247+ widgetH,
248248+ aspect,
249249+ dx,
250250+ dy,
251251+ titleFontSize,
252252+ titleX,
253253+ titleY,
254254+ valueFontSize,
255255+ valueX,
256256+ valueY,
257257+ labelFontSize,
258258+ labelX,
259259+ labelY,
260260+ minFontSize,
261261+ minX,
262262+ minY,
263263+ maxFontSize,
264264+ maxX,
265265+ maxY;
266266+267267+ // overflow values
268268+ if (obj.config.value > obj.config.max) obj.config.value = obj.config.max;
269269+ if (obj.config.value < obj.config.min) obj.config.value = obj.config.min;
270270+ obj.originalValue = kvLookup('value', config, dataset, -1, 'float');
271271+272272+ // create canvas
273273+ if (obj.config.id !== null && (document.getElementById(obj.config.id)) !== null) {
274274+ obj.canvas = Raphael(obj.config.id, "100%", "100%");
275275+ } else if (obj.config.parentNode !== null) {
276276+ obj.canvas = Raphael(obj.config.parentNode, "100%", "100%");
277277+ }
278278+279279+ if (obj.config.relativeGaugeSize === true) {
280280+ obj.canvas.setViewBox(0, 0, 200, 150, true);
281281+ }
282282+283283+ // canvas dimensions
284284+ if (obj.config.relativeGaugeSize === true) {
285285+ canvasW = 200;
286286+ canvasH = 150;
287287+ } else if (obj.config.width !== null && obj.config.height !== null) {
288288+ canvasW = obj.config.width;
289289+ canvasH = obj.config.height;
290290+ } else if (obj.config.parentNode !== null) {
291291+ obj.canvas.setViewBox(0, 0, 200, 150, true);
292292+ canvasW = 200;
293293+ canvasH = 150;
294294+ } else {
295295+ canvasW = getStyle(document.getElementById(obj.config.id), "width").slice(0, -2) * 1;
296296+ canvasH = getStyle(document.getElementById(obj.config.id), "height").slice(0, -2) * 1;
297297+ }
298298+299299+ // widget dimensions
300300+ if (obj.config.donut === true) {
301301+302302+ // DONUT *******************************
303303+304304+ // width more than height
305305+ if (canvasW > canvasH) {
306306+ widgetH = canvasH;
307307+ widgetW = widgetH;
308308+ // width less than height
309309+ } else if (canvasW < canvasH) {
310310+ widgetW = canvasW;
311311+ widgetH = widgetW;
312312+ // if height don't fit, rescale both
313313+ if (widgetH > canvasH) {
314314+ aspect = widgetH / canvasH;
315315+ widgetH = widgetH / aspect;
316316+ widgetW = widgetH / aspect;
317317+ }
318318+ // equal
319319+ } else {
320320+ widgetW = canvasW;
321321+ widgetH = widgetW;
322322+ }
323323+324324+ // delta
325325+ dx = (canvasW - widgetW) / 2;
326326+ dy = (canvasH - widgetH) / 2;
327327+328328+ // title
329329+ titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10;
330330+ titleX = dx + widgetW / 2;
331331+ titleY = dy + widgetH / 11;
332332+333333+ // value
334334+ valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18;
335335+ valueX = dx + widgetW / 2;
336336+ if (obj.config.label !== '') {
337337+ valueY = dy + widgetH / 1.85;
338338+ } else {
339339+ valueY = dy + widgetH / 1.7;
340340+ }
341341+342342+ // label
343343+ labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
344344+ labelX = dx + widgetW / 2;
345345+ labelY = valueY + labelFontSize;
346346+347347+ // min
348348+ minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
349349+ minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
350350+ minY = labelY;
351351+352352+ // max
353353+ maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10;
354354+ maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
355355+ maxY = labelY;
356356+357357+ } else {
358358+ // HALF *******************************
359359+360360+ // width more than height
361361+ if (canvasW > canvasH) {
362362+ widgetH = canvasH;
363363+ widgetW = widgetH * 1.25;
364364+ //if width doesn't fit, rescale both
365365+ if (widgetW > canvasW) {
366366+ aspect = widgetW / canvasW;
367367+ widgetW = widgetW / aspect;
368368+ widgetH = widgetH / aspect;
369369+ }
370370+ // width less than height
371371+ } else if (canvasW < canvasH) {
372372+ widgetW = canvasW;
373373+ widgetH = widgetW / 1.25;
374374+ // if height don't fit, rescale both
375375+ if (widgetH > canvasH) {
376376+ aspect = widgetH / canvasH;
377377+ widgetH = widgetH / aspect;
378378+ widgetW = widgetH / aspect;
379379+ }
380380+ // equal
381381+ } else {
382382+ widgetW = canvasW;
383383+ widgetH = widgetW * 0.75;
384384+ }
385385+386386+ // delta
387387+ dx = (canvasW - widgetW) / 2;
388388+ dy = (canvasH - widgetH) / 2;
389389+ if (obj.config.titlePosition === 'below') {
390390+ // shift whole thing down
391391+ dy -= (widgetH / 6.4);
392392+ }
393393+394394+ // title
395395+ titleFontSize = ((widgetH / 8) > obj.config.titleMinFontSize) ? (widgetH / 10) : obj.config.titleMinFontSize;
396396+ titleX = dx + widgetW / 2;
397397+ titleY = dy + (obj.config.titlePosition === 'below' ? (widgetH * 1.07) : (widgetH / 6.4));
398398+399399+ // value
400400+ valueFontSize = ((widgetH / 6.5) > obj.config.valueMinFontSize) ? (widgetH / 6.5) : obj.config.valueMinFontSize;
401401+ valueX = dx + widgetW / 2;
402402+ valueY = dy + widgetH / 1.275;
403403+404404+ // label
405405+ labelFontSize = ((widgetH / 16) > obj.config.labelMinFontSize) ? (widgetH / 16) : obj.config.labelMinFontSize;
406406+ labelX = dx + widgetW / 2;
407407+ labelY = valueY + valueFontSize / 2 + 5;
408408+409409+ // min
410410+ minFontSize = ((widgetH / 16) > obj.config.minLabelMinFontSize) ? (widgetH / 16) : obj.config.minLabelMinFontSize;
411411+ minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
412412+ minY = labelY;
413413+414414+ // max
415415+ maxFontSize = ((widgetH / 16) > obj.config.maxLabelMinFontSize) ? (widgetH / 16) : obj.config.maxLabelMinFontSize;
416416+ maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * obj.config.gaugeWidthScale) / 2;
417417+ maxY = labelY;
418418+ }
419419+420420+ // parameters
421421+ obj.params = {
422422+ canvasW: canvasW,
423423+ canvasH: canvasH,
424424+ widgetW: widgetW,
425425+ widgetH: widgetH,
426426+ dx: dx,
427427+ dy: dy,
428428+ titleFontSize: titleFontSize,
429429+ titleX: titleX,
430430+ titleY: titleY,
431431+ valueFontSize: valueFontSize,
432432+ valueX: valueX,
433433+ valueY: valueY,
434434+ labelFontSize: labelFontSize,
435435+ labelX: labelX,
436436+ labelY: labelY,
437437+ minFontSize: minFontSize,
438438+ minX: minX,
439439+ minY: minY,
440440+ maxFontSize: maxFontSize,
441441+ maxX: maxX,
442442+ maxY: maxY
443443+ };
444444+445445+ // var clear
446446+ canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null;
447447+448448+ // pki - custom attribute for generating gauge paths
449449+ obj.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) {
450450+451451+ var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path;
452452+453453+ if (donut) {
454454+ alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
455455+ Ro = w / 2 - w / 7;
456456+ Ri = Ro - w / 6.666666666666667 * gws;
457457+458458+ Cx = w / 2 + dx;
459459+ Cy = h / 1.95 + dy;
460460+461461+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
462462+ Yo = h - (h - Cy) - Ro * Math.sin(alpha);
463463+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
464464+ Yi = h - (h - Cy) - Ri * Math.sin(alpha);
465465+466466+ path = "M" + (Cx - Ri) + "," + Cy + " ";
467467+ path += "L" + (Cx - Ro) + "," + Cy + " ";
468468+ if (value > ((max - min) / 2)) {
469469+ path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " ";
470470+ }
471471+ path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
472472+ path += "L" + Xi + "," + Yi + " ";
473473+ if (value > ((max - min) / 2)) {
474474+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " ";
475475+ }
476476+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
477477+ path += "Z ";
478478+479479+ return {
480480+ path: path
481481+ };
482482+483483+ } else {
484484+ alpha = (1 - (value - min) / (max - min)) * Math.PI;
485485+ Ro = w / 2 - w / 10;
486486+ Ri = Ro - w / 6.666666666666667 * gws;
487487+488488+ Cx = w / 2 + dx;
489489+ Cy = h / 1.25 + dy;
490490+491491+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
492492+ Yo = h - (h - Cy) - Ro * Math.sin(alpha);
493493+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
494494+ Yi = h - (h - Cy) - Ri * Math.sin(alpha);
495495+496496+ path = "M" + (Cx - Ri) + "," + Cy + " ";
497497+ path += "L" + (Cx - Ro) + "," + Cy + " ";
498498+ path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " ";
499499+ path += "L" + Xi + "," + Yi + " ";
500500+ path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " ";
501501+ path += "Z ";
502502+503503+ return {
504504+ path: path
505505+ };
506506+ }
507507+508508+ // var clear
509509+ alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null;
510510+ };
511511+512512+ // ndl - custom attribute for generating needle path
513513+ obj.canvas.customAttributes.ndl = function(value, min, max, w, h, dx, dy, gws, donut) {
514514+515515+ var dlt = w * 3.5 / 100;
516516+ var dlb = w / 15;
517517+ var dw = w / 100;
518518+519519+ if (obj.config.pointerOptions.toplength != null && obj.config.pointerOptions.toplength != undefined) dlt = obj.config.pointerOptions.toplength;
520520+ if (obj.config.pointerOptions.bottomlength != null && obj.config.pointerOptions.bottomlength != undefined) dlb = obj.config.pointerOptions.bottomlength;
521521+ if (obj.config.pointerOptions.bottomwidth != null && obj.config.pointerOptions.bottomwidth != undefined) dw = obj.config.pointerOptions.bottomwidth;
522522+523523+ var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path;
524524+525525+ if (donut) {
526526+527527+ alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
528528+ Ro = w / 2 - w / 7;
529529+ Ri = Ro - w / 6.666666666666667 * gws;
530530+531531+ Cx = w / 2 + dx;
532532+ Cy = h / 1.95 + dy;
533533+534534+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
535535+ Yo = h - (h - Cy) - Ro * Math.sin(alpha);
536536+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
537537+ Yi = h - (h - Cy) - Ri * Math.sin(alpha);
538538+539539+ Xc = Xo + dlt * Math.cos(alpha);
540540+ Yc = Yo - dlt * Math.sin(alpha);
541541+ Xz = Xi - dlb * Math.cos(alpha);
542542+ Yz = Yi + dlb * Math.sin(alpha);
543543+544544+ Xa = Xz + dw * Math.sin(alpha);
545545+ Ya = Yz + dw * Math.cos(alpha);
546546+ Xb = Xz - dw * Math.sin(alpha);
547547+ Yb = Yz - dw * Math.cos(alpha);
548548+549549+ path = 'M' + Xa + ',' + Ya + ' ';
550550+ path += 'L' + Xb + ',' + Yb + ' ';
551551+ path += 'L' + Xc + ',' + Yc + ' ';
552552+ path += 'Z ';
553553+554554+ return {
555555+ path: path
556556+ };
557557+558558+ } else {
559559+ alpha = (1 - (value - min) / (max - min)) * Math.PI;
560560+ Ro = w / 2 - w / 10;
561561+ Ri = Ro - w / 6.666666666666667 * gws;
562562+563563+ Cx = w / 2 + dx;
564564+ Cy = h / 1.25 + dy;
565565+566566+ Xo = w / 2 + dx + Ro * Math.cos(alpha);
567567+ Yo = h - (h - Cy) - Ro * Math.sin(alpha);
568568+ Xi = w / 2 + dx + Ri * Math.cos(alpha);
569569+ Yi = h - (h - Cy) - Ri * Math.sin(alpha);
570570+571571+ Xc = Xo + dlt * Math.cos(alpha);
572572+ Yc = Yo - dlt * Math.sin(alpha);
573573+ Xz = Xi - dlb * Math.cos(alpha);
574574+ Yz = Yi + dlb * Math.sin(alpha);
575575+576576+ Xa = Xz + dw * Math.sin(alpha);
577577+ Ya = Yz + dw * Math.cos(alpha);
578578+ Xb = Xz - dw * Math.sin(alpha);
579579+ Yb = Yz - dw * Math.cos(alpha);
580580+581581+ path = 'M' + Xa + ',' + Ya + ' ';
582582+ path += 'L' + Xb + ',' + Yb + ' ';
583583+ path += 'L' + Xc + ',' + Yc + ' ';
584584+ path += 'Z ';
585585+586586+ return {
587587+ path: path
588588+ };
589589+ }
590590+591591+ // var clear
592592+ alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, Xc, Yc, Xz, Yz, Xa, Ya, Xb, Yb, path = null;
593593+ };
594594+595595+ // gauge
596596+ obj.gauge = obj.canvas.path().attr({
597597+ "stroke": "none",
598598+ "fill": obj.config.gaugeColor,
599599+ pki: [
600600+ obj.config.max,
601601+ obj.config.min,
602602+ obj.config.max,
603603+ obj.params.widgetW,
604604+ obj.params.widgetH,
605605+ obj.params.dx,
606606+ obj.params.dy,
607607+ obj.config.gaugeWidthScale,
608608+ obj.config.donut,
609609+ obj.config.reverse
610610+ ]
611611+ });
612612+613613+ // level
614614+ obj.level = obj.canvas.path().attr({
615615+ "stroke": "none",
616616+ "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),
617617+ pki: [
618618+ obj.config.min,
619619+ obj.config.min,
620620+ obj.config.max,
621621+ obj.params.widgetW,
622622+ obj.params.widgetH,
623623+ obj.params.dx,
624624+ obj.params.dy,
625625+ obj.config.gaugeWidthScale,
626626+ obj.config.donut,
627627+ obj.config.reverse
628628+ ]
629629+ });
630630+ if (obj.config.donut) {
631631+ obj.level.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
632632+ }
633633+634634+ if (obj.config.pointer) {
635635+ // needle
636636+ obj.needle = obj.canvas.path().attr({
637637+ "stroke": (obj.config.pointerOptions.stroke !== null && obj.config.pointerOptions.stroke !== undefined) ? obj.config.pointerOptions.stroke : "none",
638638+ "stroke-width": (obj.config.pointerOptions.stroke_width !== null && obj.config.pointerOptions.stroke_width !== undefined) ? obj.config.pointerOptions.stroke_width : 0,
639639+ "stroke-linecap": (obj.config.pointerOptions.stroke_linecap !== null && obj.config.pointerOptions.stroke_linecap !== undefined) ? obj.config.pointerOptions.stroke_linecap : "square",
640640+ "fill": (obj.config.pointerOptions.color !== null && obj.config.pointerOptions.color !== undefined) ? obj.config.pointerOptions.color : "#000000",
641641+ ndl: [
642642+ obj.config.min,
643643+ obj.config.min,
644644+ obj.config.max,
645645+ obj.params.widgetW,
646646+ obj.params.widgetH,
647647+ obj.params.dx,
648648+ obj.params.dy,
649649+ obj.config.gaugeWidthScale,
650650+ obj.config.donut
651651+ ]
652652+ });
653653+654654+ if (obj.config.donut) {
655655+ obj.needle.transform("r" + obj.config.donutStartAngle + ", " + (obj.params.widgetW / 2 + obj.params.dx) + ", " + (obj.params.widgetH / 1.95 + obj.params.dy));
656656+ }
657657+658658+ }
659659+660660+ // title
661661+ obj.txtTitle = obj.canvas.text(obj.params.titleX, obj.params.titleY, obj.config.title);
662662+ obj.txtTitle.attr({
663663+ "font-size": obj.params.titleFontSize,
664664+ "font-weight": "bold",
665665+ "font-family": obj.config.titleFontFamily,
666666+ "fill": obj.config.titleFontColor,
667667+ "fill-opacity": "1"
668668+ });
669669+ setDy(obj.txtTitle, obj.params.titleFontSize, obj.params.titleY);
670670+671671+ // value
672672+ obj.txtValue = obj.canvas.text(obj.params.valueX, obj.params.valueY, 0);
673673+ obj.txtValue.attr({
674674+ "font-size": obj.params.valueFontSize,
675675+ "font-weight": "bold",
676676+ "font-family": obj.config.valueFontFamily,
677677+ "fill": obj.config.valueFontColor,
678678+ "fill-opacity": "0"
679679+ });
680680+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
681681+682682+ // label
683683+ obj.txtLabel = obj.canvas.text(obj.params.labelX, obj.params.labelY, obj.config.label);
684684+ obj.txtLabel.attr({
685685+ "font-size": obj.params.labelFontSize,
686686+ "font-weight": "normal",
687687+ "font-family": "Arial",
688688+ "fill": obj.config.labelFontColor,
689689+ "fill-opacity": "0"
690690+ });
691691+ setDy(obj.txtLabel, obj.params.labelFontSize, obj.params.labelY);
692692+693693+ // min
694694+ var min = obj.config.min;
695695+ if (obj.config.reverse) {
696696+ min = obj.config.max;
697697+ }
698698+699699+ obj.txtMinimum = min;
700700+ if (obj.config.humanFriendly) {
701701+ obj.txtMinimum = humanFriendlyNumber(min, obj.config.humanFriendlyDecimal);
702702+ } else if (obj.config.formatNumber) {
703703+ obj.txtMinimum = formatNumber(min);
704704+ }
705705+ obj.txtMin = obj.canvas.text(obj.params.minX, obj.params.minY, obj.txtMinimum);
706706+ obj.txtMin.attr({
707707+ "font-size": obj.params.minFontSize,
708708+ "font-weight": "normal",
709709+ "font-family": "Arial",
710710+ "fill": obj.config.labelFontColor,
711711+ "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
712712+ });
713713+ setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
714714+715715+ // max
716716+ var max = obj.config.max;
717717+ if (obj.config.reverse) {
718718+ max = obj.config.min;
719719+ }
720720+ obj.txtMaximum = max;
721721+ if (obj.config.humanFriendly) {
722722+ obj.txtMaximum = humanFriendlyNumber(max, obj.config.humanFriendlyDecimal);
723723+ } else if (obj.config.formatNumber) {
724724+ obj.txtMaximum = formatNumber(max);
725725+ }
726726+ obj.txtMax = obj.canvas.text(obj.params.maxX, obj.params.maxY, obj.txtMaximum);
727727+ obj.txtMax.attr({
728728+ "font-size": obj.params.maxFontSize,
729729+ "font-weight": "normal",
730730+ "font-family": "Arial",
731731+ "fill": obj.config.labelFontColor,
732732+ "fill-opacity": (obj.config.hideMinMax || obj.config.donut) ? "0" : "1"
733733+ });
734734+ setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
735735+736736+ var defs = obj.canvas.canvas.childNodes[1];
737737+ var svg = "http://www.w3.org/2000/svg";
738738+739739+ if (ie !== 'undefined' && ie < 9) {
740740+ // VML mode - no SVG & SVG filter support
741741+ } else if (ie !== 'undefined') {
742742+ onCreateElementNsReady(function() {
743743+ obj.generateShadow(svg, defs);
744744+ });
745745+ } else {
746746+ obj.generateShadow(svg, defs);
747747+ }
748748+749749+ // var clear
750750+ defs, svg = null;
751751+752752+ // set value to display
753753+ if (obj.config.textRenderer) {
754754+ obj.originalValue = obj.config.textRenderer(obj.originalValue);
755755+ } else if (obj.config.humanFriendly) {
756756+ obj.originalValue = humanFriendlyNumber(obj.originalValue, obj.config.humanFriendlyDecimal) + obj.config.symbol;
757757+ } else if (obj.config.formatNumber) {
758758+ obj.originalValue = formatNumber(obj.originalValue) + obj.config.symbol;
759759+ } else {
760760+ obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol;
761761+ }
762762+763763+ if (obj.config.counter === true) {
764764+ //on each animation frame
765765+ eve.on("raphael.anim.frame." + (obj.level.id), function() {
766766+ var currentValue = obj.level.attr("pki")[0];
767767+ if (obj.config.reverse) {
768768+ currentValue = (obj.config.max * 1) + (obj.config.min * 1) - (obj.level.attr("pki")[0] * 1);
769769+ }
770770+ if (obj.config.textRenderer) {
771771+ obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue)));
772772+ } else if (obj.config.humanFriendly) {
773773+ obj.txtValue.attr("text", humanFriendlyNumber(Math.floor(currentValue), obj.config.humanFriendlyDecimal) + obj.config.symbol);
774774+ } else if (obj.config.formatNumber) {
775775+ obj.txtValue.attr("text", formatNumber(Math.floor(currentValue)) + obj.config.symbol);
776776+ } else {
777777+ obj.txtValue.attr("text", (currentValue * 1).toFixed(obj.config.decimals) + obj.config.symbol);
778778+ }
779779+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
780780+ currentValue = null;
781781+ });
782782+ //on animation end
783783+ eve.on("raphael.anim.finish." + (obj.level.id), function() {
784784+ obj.txtValue.attr({
785785+ "text": obj.originalValue
786786+ });
787787+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
788788+ });
789789+ } else {
790790+ //on animation start
791791+ eve.on("raphael.anim.start." + (obj.level.id), function() {
792792+ obj.txtValue.attr({
793793+ "text": obj.originalValue
794794+ });
795795+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
796796+ });
797797+ }
798798+799799+ // animate gauge level, value & label
800800+ var rvl = obj.config.value;
801801+ if (obj.config.reverse) {
802802+ rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
803803+ }
804804+ obj.level.animate({
805805+ pki: [
806806+ rvl,
807807+ obj.config.min,
808808+ obj.config.max,
809809+ obj.params.widgetW,
810810+ obj.params.widgetH,
811811+ obj.params.dx,
812812+ obj.params.dy,
813813+ obj.config.gaugeWidthScale,
814814+ obj.config.donut,
815815+ obj.config.reverse
816816+ ]
817817+ }, obj.config.startAnimationTime, obj.config.startAnimationType);
818818+819819+ if (obj.config.pointer) {
820820+ obj.needle.animate({
821821+ ndl: [
822822+ rvl,
823823+ obj.config.min,
824824+ obj.config.max,
825825+ obj.params.widgetW,
826826+ obj.params.widgetH,
827827+ obj.params.dx,
828828+ obj.params.dy,
829829+ obj.config.gaugeWidthScale,
830830+ obj.config.donut
831831+ ]
832832+ }, obj.config.startAnimationTime, obj.config.startAnimationType);
833833+ }
834834+835835+ obj.txtValue.animate({
836836+ "fill-opacity": (obj.config.hideValue) ? "0" : "1"
837837+ }, obj.config.startAnimationTime, obj.config.startAnimationType);
838838+ obj.txtLabel.animate({
839839+ "fill-opacity": "1"
840840+ }, obj.config.startAnimationTime, obj.config.startAnimationType);
841841+};
842842+843843+/** Refresh gauge level */
844844+JustGage.prototype.refresh = function(val, max) {
845845+846846+ var obj = this;
847847+ var displayVal, color, max = max || null;
848848+849849+ // set new max
850850+ if (max !== null) {
851851+ obj.config.max = max;
852852+ // TODO: update customSectors
853853+854854+ obj.txtMaximum = obj.config.max;
855855+ if (obj.config.humanFriendly) {
856856+ obj.txtMaximum = humanFriendlyNumber(obj.config.max, obj.config.humanFriendlyDecimal);
857857+ } else if (obj.config.formatNumber) {
858858+ obj.txtMaximum = formatNumber(obj.config.max);
859859+ }
860860+ if (!obj.config.reverse) {
861861+ obj.txtMax.attr({
862862+ "text": obj.txtMaximum
863863+ });
864864+ setDy(obj.txtMax, obj.params.maxFontSize, obj.params.maxY);
865865+ } else {
866866+ obj.txtMin.attr({
867867+ "text": obj.txtMaximum
868868+ });
869869+ setDy(obj.txtMin, obj.params.minFontSize, obj.params.minY);
870870+ }
871871+ }
872872+873873+ // overflow values
874874+ displayVal = val;
875875+ if ((val * 1) > (obj.config.max * 1)) {
876876+ val = (obj.config.max * 1);
877877+ }
878878+ if ((val * 1) < (obj.config.min * 1)) {
879879+ val = (obj.config.min * 1);
880880+ }
881881+882882+ color = getColor(val, (val - obj.config.min) / (obj.config.max - obj.config.min), obj.config.levelColors, obj.config.noGradient, obj.config.customSectors);
883883+884884+ if (obj.config.textRenderer) {
885885+ displayVal = obj.config.textRenderer(displayVal);
886886+ } else if (obj.config.humanFriendly) {
887887+ displayVal = humanFriendlyNumber(displayVal, obj.config.humanFriendlyDecimal) + obj.config.symbol;
888888+ } else if (obj.config.formatNumber) {
889889+ displayVal = formatNumber((displayVal * 1).toFixed(obj.config.decimals)) + obj.config.symbol;
890890+ } else {
891891+ displayVal = (displayVal * 1).toFixed(obj.config.decimals) + obj.config.symbol;
892892+ }
893893+ obj.originalValue = displayVal;
894894+ obj.config.value = val * 1;
895895+896896+ if (!obj.config.counter) {
897897+ obj.txtValue.attr({
898898+ "text": displayVal
899899+ });
900900+ setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY);
901901+ }
902902+903903+ var rvl = obj.config.value;
904904+ if (obj.config.reverse) {
905905+ rvl = (obj.config.max * 1) + (obj.config.min * 1) - (obj.config.value * 1);
906906+ }
907907+ obj.level.animate({
908908+ pki: [
909909+ rvl,
910910+ obj.config.min,
911911+ obj.config.max,
912912+ obj.params.widgetW,
913913+ obj.params.widgetH,
914914+ obj.params.dx,
915915+ obj.params.dy,
916916+ obj.config.gaugeWidthScale,
917917+ obj.config.donut,
918918+ obj.config.reverse
919919+ ],
920920+ "fill": color
921921+ }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
922922+923923+ if (obj.config.pointer) {
924924+ obj.needle.animate({
925925+ ndl: [
926926+ rvl,
927927+ obj.config.min,
928928+ obj.config.max,
929929+ obj.params.widgetW,
930930+ obj.params.widgetH,
931931+ obj.params.dx,
932932+ obj.params.dy,
933933+ obj.config.gaugeWidthScale,
934934+ obj.config.donut
935935+ ]
936936+ }, obj.config.refreshAnimationTime, obj.config.refreshAnimationType);
937937+ }
938938+939939+ // var clear
940940+ obj, displayVal, color, max = null;
941941+};
942942+943943+/** Generate shadow */
944944+JustGage.prototype.generateShadow = function(svg, defs) {
945945+946946+ var obj = this;
947947+ var sid = "inner-shadow-" + obj.config.id;
948948+ var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3;
949949+950950+ // FILTER
951951+ gaussFilter = document.createElementNS(svg, "filter");
952952+ gaussFilter.setAttribute("id", sid);
953953+ defs.appendChild(gaussFilter);
954954+955955+ // offset
956956+ feOffset = document.createElementNS(svg, "feOffset");
957957+ feOffset.setAttribute("dx", 0);
958958+ feOffset.setAttribute("dy", obj.config.shadowVerticalOffset);
959959+ gaussFilter.appendChild(feOffset);
960960+961961+ // blur
962962+ feGaussianBlur = document.createElementNS(svg, "feGaussianBlur");
963963+ feGaussianBlur.setAttribute("result", "offset-blur");
964964+ feGaussianBlur.setAttribute("stdDeviation", obj.config.shadowSize);
965965+ gaussFilter.appendChild(feGaussianBlur);
966966+967967+ // composite 1
968968+ feComposite1 = document.createElementNS(svg, "feComposite");
969969+ feComposite1.setAttribute("operator", "out");
970970+ feComposite1.setAttribute("in", "SourceGraphic");
971971+ feComposite1.setAttribute("in2", "offset-blur");
972972+ feComposite1.setAttribute("result", "inverse");
973973+ gaussFilter.appendChild(feComposite1);
974974+975975+ // flood
976976+ feFlood = document.createElementNS(svg, "feFlood");
977977+ feFlood.setAttribute("flood-color", "black");
978978+ feFlood.setAttribute("flood-opacity", obj.config.shadowOpacity);
979979+ feFlood.setAttribute("result", "color");
980980+ gaussFilter.appendChild(feFlood);
981981+982982+ // composite 2
983983+ feComposite2 = document.createElementNS(svg, "feComposite");
984984+ feComposite2.setAttribute("operator", "in");
985985+ feComposite2.setAttribute("in", "color");
986986+ feComposite2.setAttribute("in2", "inverse");
987987+ feComposite2.setAttribute("result", "shadow");
988988+ gaussFilter.appendChild(feComposite2);
989989+990990+ // composite 3
991991+ feComposite3 = document.createElementNS(svg, "feComposite");
992992+ feComposite3.setAttribute("operator", "over");
993993+ feComposite3.setAttribute("in", "shadow");
994994+ feComposite3.setAttribute("in2", "SourceGraphic");
995995+ gaussFilter.appendChild(feComposite3);
996996+997997+ // set shadow
998998+ if (!obj.config.hideInnerShadow) {
999999+ obj.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + sid + ")");
10001000+ obj.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + sid + ")");
10011001+ }
10021002+10031003+ // var clear
10041004+ gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null;
10051005+};
10061006+10071007+//
10081008+// tiny helper function to lookup value of a key from two hash tables
10091009+// if none found, return defaultvalue
10101010+//
10111011+// key: string
10121012+// tablea: object
10131013+// tableb: DOMStringMap|object
10141014+// defval: string|integer|float|null
10151015+// datatype: return datatype
10161016+// delimiter: delimiter to be used in conjunction with datatype formatting
10171017+//
10181018+function kvLookup(key, tablea, tableb, defval, datatype, delimiter) {
10191019+ var val = defval;
10201020+ var canConvert = false;
10211021+ if (!(key === null || key === undefined)) {
10221022+ if (tableb !== null && tableb !== undefined && typeof tableb === "object" && key in tableb) {
10231023+ val = tableb[key];
10241024+ canConvert = true;
10251025+ } else if (tablea !== null && tablea !== undefined && typeof tablea === "object" && key in tablea) {
10261026+ val = tablea[key];
10271027+ canConvert = true;
10281028+ } else {
10291029+ val = defval;
10301030+ }
10311031+ if (canConvert === true) {
10321032+ if (datatype !== null && datatype !== undefined) {
10331033+ switch (datatype) {
10341034+ case 'int':
10351035+ val = parseInt(val, 10);
10361036+ break;
10371037+ case 'float':
10381038+ val = parseFloat(val);
10391039+ break;
10401040+ default:
10411041+ break;
10421042+ }
10431043+ }
10441044+ }
10451045+ }
10461046+ return val;
10471047+};
10481048+10491049+/** Get color for value */
10501050+function getColor(val, pct, col, noGradient, custSec) {
10511051+10521052+ var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color;
10531053+ var noGradient = noGradient || custSec.length > 0;
10541054+10551055+ if (custSec.length > 0) {
10561056+ for (var i = 0; i < custSec.length; i++) {
10571057+ if (val > custSec[i].lo && val <= custSec[i].hi) {
10581058+ return custSec[i].color;
10591059+ }
10601060+ }
10611061+ }
10621062+10631063+ no = col.length;
10641064+ if (no === 1) return col[0];
10651065+ inc = (noGradient) ? (1 / no) : (1 / (no - 1));
10661066+ colors = [];
10671067+ for (i = 0; i < col.length; i++) {
10681068+ percentage = (noGradient) ? (inc * (i + 1)) : (inc * i);
10691069+ rval = parseInt((cutHex(col[i])).substring(0, 2), 16);
10701070+ gval = parseInt((cutHex(col[i])).substring(2, 4), 16);
10711071+ bval = parseInt((cutHex(col[i])).substring(4, 6), 16);
10721072+ colors[i] = {
10731073+ pct: percentage,
10741074+ color: {
10751075+ r: rval,
10761076+ g: gval,
10771077+ b: bval
10781078+ }
10791079+ };
10801080+ }
10811081+10821082+ if (pct === 0) {
10831083+ return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')';
10841084+ }
10851085+10861086+ for (var j = 0; j < colors.length; j++) {
10871087+ if (pct <= colors[j].pct) {
10881088+ if (noGradient) {
10891089+ return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')';
10901090+ } else {
10911091+ lower = colors[j - 1];
10921092+ upper = colors[j];
10931093+ range = upper.pct - lower.pct;
10941094+ rangePct = (pct - lower.pct) / range;
10951095+ pctLower = 1 - rangePct;
10961096+ pctUpper = rangePct;
10971097+ color = {
10981098+ r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
10991099+ g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
11001100+ b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
11011101+ };
11021102+ return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';
11031103+ }
11041104+ }
11051105+ }
11061106+11071107+}
11081108+11091109+/** Fix Raphael display:none tspan dy attribute bug */
11101110+function setDy(elem, fontSize, txtYpos) {
11111111+ if ((!ie || ie > 9) && elem.node.firstChild.attributes.dy) {
11121112+ elem.node.firstChild.attributes.dy.value = 0;
11131113+ }
11141114+}
11151115+11161116+/** Random integer */
11171117+function getRandomInt(min, max) {
11181118+ return Math.floor(Math.random() * (max - min + 1)) + min;
11191119+}
11201120+11211121+/** Cut hex */
11221122+function cutHex(str) {
11231123+ return (str.charAt(0) == "#") ? str.substring(1, 7) : str;
11241124+}
11251125+11261126+/** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */
11271127+function humanFriendlyNumber(n, d) {
11281128+ var p, d2, i, s;
11291129+11301130+ p = Math.pow;
11311131+ d2 = p(10, d);
11321132+ i = 7;
11331133+ while (i) {
11341134+ s = p(10, i-- * 3);
11351135+ if (s <= n) {
11361136+ n = Math.round(n * d2 / s) / d2 + "KMGTPE" [i];
11371137+ }
11381138+ }
11391139+ return n;
11401140+}
11411141+11421142+/** Format numbers with commas - From: http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript */
11431143+function formatNumber(x) {
11441144+ var parts = x.toString().split(".");
11451145+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
11461146+ return parts.join(".");
11471147+}
11481148+11491149+/** Get style */
11501150+function getStyle(oElm, strCssRule) {
11511151+ var strValue = "";
11521152+ if (document.defaultView && document.defaultView.getComputedStyle) {
11531153+ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
11541154+ } else if (oElm.currentStyle) {
11551155+ strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) {
11561156+ return p1.toUpperCase();
11571157+ });
11581158+ strValue = oElm.currentStyle[strCssRule];
11591159+ }
11601160+ return strValue;
11611161+}
11621162+11631163+/** Create Element NS Ready */
11641164+function onCreateElementNsReady(func) {
11651165+ if (document.createElementNS !== undefined) {
11661166+ func();
11671167+ } else {
11681168+ setTimeout(function() {
11691169+ onCreateElementNsReady(func);
11701170+ }, 100);
11711171+ }
11721172+}
11731173+11741174+/** Get IE version */
11751175+// ----------------------------------------------------------
11761176+// A short snippet for detecting versions of IE in JavaScript
11771177+// without resorting to user-agent sniffing
11781178+// ----------------------------------------------------------
11791179+// If you're not in IE (or IE version is less than 5) then:
11801180+// ie === undefined
11811181+// If you're in IE (>=5) then you can determine which version:
11821182+// ie === 7; // IE7
11831183+// Thus, to detect IE:
11841184+// if (ie) {}
11851185+// And to detect the version:
11861186+// ie === 6 // IE6
11871187+// ie > 7 // IE8, IE9 ...
11881188+// ie < 9 // Anything less than IE9
11891189+// ----------------------------------------------------------
11901190+// UPDATE: Now using Live NodeList idea from @jdalton
11911191+var ie = (function() {
11921192+11931193+ var undef,
11941194+ v = 3,
11951195+ div = document.createElement('div'),
11961196+ all = div.getElementsByTagName('i');
11971197+11981198+ while (
11991199+ div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
12001200+ all[0]
12011201+ );
12021202+ return v > 4 ? v : undef;
12031203+}());
12041204+12051205+// extend target object with second object
12061206+function extend(out) {
12071207+ out = out || {};
12081208+12091209+ for (var i = 1; i < arguments.length; i++) {
12101210+ if (!arguments[i])
12111211+ continue;
12121212+12131213+ for (var key in arguments[i]) {
12141214+ if (arguments[i].hasOwnProperty(key))
12151215+ out[key] = arguments[i][key];
12161216+ }
12171217+ }
12181218+12191219+ return out;
12201220+};