From de8af028e54e4e71b942d7cef8aca7299eccb519 Mon Sep 17 00:00:00 2001 From: Martin Donnelly Date: Thu, 11 Aug 2016 15:34:52 +0100 Subject: [PATCH] Updated to use PGSql backend Before convertion of graph object to use AMCharts.. --- app.js | 4 +- app/css/notification.css | 136 +++ app/js/mdot.js | 61 +- app/lib/amcharts.js | 395 ++++++++ app/lib/amstock.js | 104 ++ app/lib/notification.js | 1958 ++++++++++++++++++++++++++++++++++++ app/lib/serial.js | 94 ++ app/test.html | 20 +- bower.json | 4 +- lib/mdot/api.js | 25 +- lib/mdot/apiv2.js | 51 + lib/mdot/mdot.js | 80 +- lib/server/db-connector.js | 21 + lib/server/db-mdot.js | 88 ++ lib/server/db-save.js | 128 +++ package.json | 22 +- 16 files changed, 3125 insertions(+), 66 deletions(-) create mode 100644 app/css/notification.css create mode 100644 app/lib/amcharts.js create mode 100644 app/lib/amstock.js create mode 100644 app/lib/notification.js create mode 100644 app/lib/serial.js create mode 100644 lib/mdot/apiv2.js create mode 100644 lib/server/db-connector.js create mode 100644 lib/server/db-mdot.js create mode 100644 lib/server/db-save.js diff --git a/app.js b/app.js index f0f0f9c..c8b4747 100644 --- a/app.js +++ b/app.js @@ -29,8 +29,7 @@ var isProduction = false; var mdotApi = require('./lib/mdot/api.js'); - - +var mdotApiV2 = require('./lib/mdot/apiv2.js'); process.env.NODE_ENV = process.env.NODE_ENV || 'development'; @@ -81,6 +80,7 @@ function originIsAllowed(origin) { // glue routes mdotApi(app); +mdotApiV2(app); //app.get('/api/mdot/:id', mDot.getData); diff --git a/app/css/notification.css b/app/css/notification.css new file mode 100644 index 0000000..a3b710b --- /dev/null +++ b/app/css/notification.css @@ -0,0 +1,136 @@ +/*! + * Notification.js + * + * A well designed, highly customizable and lightweigth notification library. + * + * @author Dominique Müller + * @copyright Dominique Müller 2015 + * @license MIT + * @link Github + * @version 1.0.0 + */ + +/* Notification container */ + +.notification { + position: fixed; + z-index: 9999; + overflow: hidden; + box-shadow: 0 1px 2px rgba(0,0,0,0); + -moz-box-sizing: border-box; + box-sizing: border-box; + opacity: 0; +} + +.notification.is-visible { + box-shadow: 0 1px 2px rgba(0,0,0,.15); + opacity: 1; +} + +.notification-background { + position: absolute; + top: 50%; + z-index: -1; + width: 200%; + padding-bottom: 200%; + border-radius: 50%; + -webkit-transform: translateY(-50%) scale(0); + transform: translateY(-50%) scale(0); +} + +.notification.is-visible > .notification-background { + -webkit-transform: translateY(-50%) scale(1); + transform: translateY(-50%) scale(1); +} + +/* Notification symbol */ + +.notification-symbol { + position: absolute; + top: 50%; + opacity: 0; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} + +.notification.is-visible > .notification-symbol { + opacity: 1; +} + +/* Notification default success symbol */ + +.notification-symbol-success > polyline { + transition: stroke-dashoffset 0s linear 2s; + + stroke-dashoffset: 23; +} + +.notification.is-visible > .notification-symbol-success > polyline { + transition: stroke-dashoffset .8s ease; + + stroke-dashoffset: 0; +} + +/* Notification default error symbol */ + +.notification-symbol-error > line { + transition: stroke-dashoffset 0s linear 2s; + + stroke-dashoffset: 17; +} + +.notification.is-visible > .notification-symbol-error > line:nth-child(1) { + transition: stroke-dashoffset .5s ease; + + stroke-dashoffset: 0; +} + +.notification.is-visible > .notification-symbol-error > line:nth-child(2) { + transition: stroke-dashoffset .5s ease .4s; + + stroke-dashoffset: 0; +} + +/* Notification message */ + +.notification-message { + margin-top: 0; + margin-bottom: 0; + opacity: 0; + -webkit-transform: translateX(-22px); + transform: translateX(-22px); +} + +.notification.is-visible > .notification-message { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); +} + +/* Notification button */ + +.notification-btn { + position: absolute; + top: 50%; + right: 10px; + cursor: pointer; + background-color: transparent; + border: none; + opacity: 0; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} + +.notification-btn:hover, +.notification-btn:focus, +.notification-btn:active { + padding: inherit; +} + +.notification-btn::-moz-focus-inner { + border: 0; +} + +.notification.is-visible > .notification-btn { + opacity: .5; +} diff --git a/app/js/mdot.js b/app/js/mdot.js index bd806a4..6e936d7 100644 --- a/app/js/mdot.js +++ b/app/js/mdot.js @@ -42,22 +42,18 @@ _.invoke(DeviceCollection.toArray(), 'destroy'); - this.temporal = {low: 0,high: 0}; _(this.get('events')).each(function(i) { - i.evt.decoded = this.decoder(i.evt.data); - i.evt.dateTime = this.dateTime(i.timestamp.$date); - - if (this.temporal.low === 0 || this.temporal.low > i.timestamp.$date) { - this.temporal.low = i.timestamp.$date; + if (this.temporal.low === 0 || this.temporal.low > i.timestamp) { + this.temporal.low = i.timestamp; } - if (this.temporal.high === 0 || this.temporal.high < i.timestamp.$date) { - this.temporal.high = i.timestamp.$date; + if (this.temporal.high === 0 || this.temporal.high < i.timestamp) { + this.temporal.high = i.timestamp; } - tempCollection.add({dt: i.evt.dateTime.dateTime,lux: i.evt.decoded.light, temp: i.evt.decoded.temp, co2: i.evt.decoded.co2, humid: i.evt.decoded.humid, noise: i.evt.decoded.noise}); + tempCollection.add({dt: i.timestamp,lux: i.lux, temp: i.temp, co2: i.co2, humid: i.humidity, noise: i.sound}); }, this); DeviceCollection.temporal = this.temporal; @@ -92,10 +88,6 @@ model: EventsModel, url: 'https://qz0da4.internetofthings.ibmcloud.com/api/v0002/historian/types/mDot/devices/', initialize: function() { - this.on('all',function(d) { - console.log('Collection:all',d); - - }); this.on('update', function() { // Console.log('Collection:update',this); }); @@ -109,6 +101,7 @@ console.log('ItemView:Init'); // This.render(); }, render: function() { + console.log('ItemView:Render'); _(this.model.events).each(function(i) { this.$el.append(this.template({item: i})); @@ -143,6 +136,7 @@ }, render: function() { console.log('MDOT:render'); + var that = this; this.$el.empty(); this.collection.each(function(model) { @@ -185,13 +179,12 @@ },changeDate: function(elm) { console.log('ChangeDate', elm); - if (Number.isNaN(elm.currentTarget.valueAsNumber)) { + if (elm.currentTarget.valueAsDate === null) { this.model.unset(elm.currentTarget.id); } else { - this.model.set(elm.currentTarget.id, elm.currentTarget.valueAsNumber); + this.model.set(elm.currentTarget.id, elm.currentTarget.valueAsDate); } - }, changeDevice: function() { var newDevice; @@ -204,17 +197,16 @@ var fetchObj = {beforeSend: sendAuthentication}; var rangeObj = {start: null, end: null}; console.log('MainView:Updatedevice'); - console.log(this.model); if (this.model.has('from') && this.model.has('to')) { rangeObj.start = this.model.get('from'); rangeObj.end = this.model.get('to'); fetchObj.data = $.param(rangeObj); - console.log(fetchObj.data); } if (this.model.has('device')) { // FetchObj.data = $.param({key:'"'+ this.model.get('device') + '"'}); // this.collection.url = 'https://qz0da4.internetofthings.ibmcloud.com/api/v0002/historian/types/mDot/devices/' + this.model.get('device'); - this.collection.url = '/api/mdot/' + this.model.get('device'); + // this.collection.url = '/api/mdot/' + this.model.get('device'); + this.collection.url = '/apiv2/mdot/' + this.model.get('device'); // this.collection.url = 'http://127.0.0.1:5984/mdot/_design/getDevice/_view/getDevice'; this.collection.fetch(fetchObj); } else { @@ -293,15 +285,19 @@ this.$baseline.empty(); scale = 124 / ceilingLimit; - // Var xstep = (280 - 46) / 100; - xstep = 2.34; - startX = 46 + (100 - points.length) * xstep; + xstep = 234 / points.length; + console.log('Points length:', points.length); + console.log(xstep); + //Xstep = 2.34; + startX = 46 ; + if (points.length < 100) { + startX = 46 + (100 - points.length) * xstep; + } calcArray = []; for (var x = 0;x < points.length;x++) { calcArray.push((startX + (x * xstep)).toFixed(2) + ',' + (136 - ((points[x]) * scale)).toFixed(2)); - circle = document.createElementNS(this.xmlns,'circle'); title = document.createElementNS(this.xmlns,'title'); if (parseInt(this.mode) === 5) { @@ -311,18 +307,21 @@ } else { occupied = 'red'; } + if (occupied !== 'red') { + circle = document.createElementNS(this.xmlns,'circle'); + circle.setAttributeNS(null,'fill',occupied); + circle.setAttributeNS(null,'cx',(startX + (x * xstep)).toFixed(2).toString()); + circle.setAttributeNS(null,'cy',(136 - ((points[x]) * scale)).toFixed(2).toString()); + circle.setAttributeNS(null,'r','2'); + circle.setAttributeNS(null,'stroke-width','1'); - circle.setAttributeNS(null,'fill',occupied); - circle.setAttributeNS(null,'cx',(startX + (x * xstep)).toFixed(2).toString()); - circle.setAttributeNS(null,'cy',(136 - ((points[x]) * scale)).toFixed(2).toString()); - circle.setAttributeNS(null,'r','2'); - circle.setAttributeNS(null,'stroke-width','1'); + title.textContent = data[x]; - title.textContent = data[x]; + circle.appendChild(title); - circle.appendChild(title); + this.$datapoints[0].appendChild(circle); - this.$datapoints[0].appendChild(circle); + } } console.log('This.mode = ', this.mode); diff --git a/app/lib/amcharts.js b/app/lib/amcharts.js new file mode 100644 index 0000000..f603814 --- /dev/null +++ b/app/lib/amcharts.js @@ -0,0 +1,395 @@ +(function(){var d;window.AmCharts?d=window.AmCharts:(d={},window.AmCharts=d,d.themes={},d.maps={},d.inheriting={},d.charts=[],d.onReadyArray=[],d.useUTC=!1,d.updateRate=60,d.uid=0,d.lang={},d.translations={},d.mapTranslations={},d.windows={},d.initHandlers=[],d.amString="am",d.pmString="pm");d.Class=function(a){var b=function(){arguments[0]!==d.inheriting&&(this.events={},this.construct.apply(this,arguments))};a.inherits?(b.prototype=new a.inherits(d.inheriting),b.base=a.inherits.prototype,delete a.inherits): +(b.prototype.createEvents=function(){for(var a=0;ad.IEversion&&0b)return a;h=-1;for(a=(k=a.split(/\r\n|\n|\r/)).length;++hb;k[h]+=d.trim(g.slice(0,f))+((g=g.slice(f)).length?c:""))f=2==e||(f=g.slice(0,b+1).match(/\S*(\s)?$/))[1]?b:f.input.length-f[0].length||1==e&&b||f.input.length+(f=g.slice(b).match(/^\S*/))[0].length;g=d.trim(g)}return k.join(c)};d.trim=function(a){return a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")};d.wrappedText=function(a,b,c,e,h,f,g,k){var l=d.text(a,b,c,e,h,f,g);if(l){var m=l.getBBox();if(m.width>k){var n="\n";d.isModern||(n="
");k=Math.floor(k/(m.width/ +b.length));2c&&(a=c);return a};d.isDefined=function(a){return void 0===a?!1:!0};d.stripNumbers=function(a){return a.replace(/[0-9]+/g,"")};d.roundTo=function(a,b){if(0>b)return a;var c=Math.pow(10,b);return Math.round(a*c)/c};d.toFixed=function(a,b){var c=String(Math.round(a*Math.pow(10,b)));if(0=g[b].contains){var l=a-Math.floor(a/g[b].contains)*g[b].contains;"ss"==b?(l=d.formatNumber(l,f),1==l.split(k)[0].length&&(l="0"+l)):l=d.roundTo(l,f.precision);("mm"==b||"hh"==b)&&10>l&&(l="0"+l);c=l+""+e[b]+""+c;a=Math.floor(a/g[b].contains);b=g[b].nextInterval;return d.formatDuration(a,b,c,e,h,f)}"ss"==b&&(a=d.formatNumber(a, +f),1==a.split(k)[0].length&&(a="0"+a));("mm"==b||"hh"==b)&&10>a&&(a="0"+a);c=a+""+e[b]+""+c;if(g[h].count>g[b].count)for(a=g[b].count;aa?"-":"";a=Math.abs(a);var k=String(a),l=!1;-1!= +k.indexOf("e")&&(l=!0);0<=c&&!l&&(k=d.toFixed(a,c));var m="";if(l)m=k;else{var k=k.split("."),l=String(k[0]),n;for(n=l.length;0<=n;n-=3)m=n!=l.length?0!==n?l.substring(n-3,n)+b+m:l.substring(n-3,n)+m:l.substring(n-3,n);void 0!==k[1]&&(m=m+f+k[1]);void 0!==c&&0=c.x-5&&a<=c.x+c.width+5&&b>=c.y-5&&b<=c.y+c.height+5?!0:!1};d.isPercents=function(a){if(-1!=String(a).indexOf("%"))return!0};d.formatValue=function(a,b,c,e,h,f,g,k){if(b){void 0=== +h&&(h="");var l;for(l=0;la&&(g="-");a=Math.abs(a);if(1=b[k].number&&(l=a/b[k].number,m=Number(e.precision),1>m&&(m=1),c=d.roundTo(l,m),m=d.formatNumber(c,{precision:-1,decimalSeparator:e.decimalSeparator,thousandsSeparator:e.thousandsSeparator}),!h||l==c)){f=g+""+m+""+b[k].prefix;break}}else for(k=0;k"==a&&(a="easeOutSine");"<"==a&&(a="easeInSine");"elastic"==a&&(a="easeOutElastic");return a};d.getObjById=function(a,b){var c,e;for(e=0;e"));return a};d.fixBrakes=function(a){if(d.isModern){var b=RegExp("
","g");a&&(a=a.replace(b,"\n"))}else a=d.fixNewLines(a);return a};d.deleteObject=function(a,b){if(a){if(void 0===b||null===b)b=20;if(0!==b)if("[object Array]"===Object.prototype.toString.call(a))for(var c=0;cb)return e/2*b*b+c;b--;return-e/2*(b*(b-2)-1)+c};d.easeInSine=function(a,b,c,e,d){return-e*Math.cos(b/d*(Math.PI/2))+e+c};d.easeOutSine=function(a,b,c,e,d){return e*Math.sin(b/d*(Math.PI/2))+c};d.easeOutElastic= +function(a,b,c,e,d){a=1.70158;var f=0,g=e;if(0===b)return c;if(1==(b/=d))return c+e;f||(f=.3*d);gb?Math.abs(b)-1:Math.abs(b);var d;for(d=0;db?Number("0."+c+String(a)):Number(String(a)+c)};d.setCN= +function(a,b,c,e){if(a.addClassNames&&b&&(b=b.node)&&c){var d=b.getAttribute("class");a=a.classNamePrefix+"-";e&&(a="");d?b.setAttribute("class",d+" "+a+c):b.setAttribute("class",a+c)}};d.removeCN=function(a,b,c){b&&(b=b.node)&&c&&(b=b.classList)&&b.remove(a.classNamePrefix+"-"+c)};d.parseDefs=function(a,b){for(var c in a){var e=typeof a[c];if(0a&&(a=3)):a=this.width/this.minHorizontalGap,this.gridCountR=Math.max(a,1)):this.gridCountR=this.gridCount;this.axisWidth=this.axisLine.axisWidth;this.addTitle()},setOrientation:function(a){this.orientation=a?"H":"V"},addTitle:function(){var a= +this.title;this.titleLabel=null;if(a){var b=this.chart,c=this.titleColor;void 0===c&&(c=b.color);var e=this.titleFontSize;isNaN(e)&&(e=b.fontSize+1);a=d.text(b.container,a,c,b.fontFamily,e,this.titleAlign,this.titleBold);d.setCN(b,a,this.bcn+"title");this.titleLabel=a}},positionTitle:function(){var a=this.titleLabel;if(a){var b,c,e=this.labelsSet,h={};0this.autoRotateCount&&!isNaN(this.autoRotateAngle)&&(this.labelRotationR=this.autoRotateAngle),a=k;a<=B;a++){p=q+z*(a+Math.floor((D-q)/z))-C;"DD"==A&&(p+=36E5);p=d.resetDateToMin(new Date(p),A,u,t).getTime();"MM"==A&&(h=(p-l)/z,1.5<=(p-l)/z&&(p=p-(h-1)*z+d.getPeriodDuration("DD", +3),p=d.resetDateToMin(new Date(p),A,1).getTime(),C+=z));h=(p-this.startTime)*this.stepWidth;if("radar"==b.type){if(h=this.axisWidth-h,0>h||h>this.axisWidth)continue}else this.rotate?"date"==this.type&&"middle"==this.gridPosition&&(J=-z*this.stepWidth/2):"date"==this.type&&(h=this.axisWidth-h);f=!1;this.nextPeriod[g]&&(f=this.checkPeriodChange(this.nextPeriod[g],1,p,l,g));l=!1;f&&this.markPeriodChange?(f=this.dateFormatsObject[this.nextPeriod[g]],this.twoLineMode&&(f=this.dateFormatsObject[g]+"\n"+ +f,f=d.fixBrakes(f)),l=!0):f=this.dateFormatsObject[g];r||(l=!1);this.currentDateFormat=f;f=d.formatDate(new Date(p),f,b);if(a==k&&!c||a==B&&!e)f=" ";this.labelFunction&&(f=this.labelFunction(f,new Date(p),this,A,u,m).toString());this.boldLabels&&(l=!0);m=new this.axisItemRenderer(this,h,f,!1,n,J,!1,l);this.pushAxisItem(m);m=l=p;if(!isNaN(v))for(h=1;hb||b>this.height)return;if(isNaN(b)){this.hideBalloon(); +return}b=this.adjustBalloonCoordinate(b,e);e=this.coordinateToValue(b)}else{if(0>a||a>this.width)return;if(isNaN(a)){this.hideBalloon();return}a=this.adjustBalloonCoordinate(a,e);e=this.coordinateToValue(a)}var f;if(d=this.chart.chartCursor)f=d.index;if(this.balloon&&void 0!==e&&this.balloon.enabled){if(this.balloonTextFunction){if("date"==this.type||!0===this.parseDates)e=new Date(e);e=this.balloonTextFunction(e)}else this.balloonText?e=this.formatBalloonText(this.balloonText,f,c):isNaN(e)||(e=this.formatValue(e, +c));if(a!=this.prevBX||b!=this.prevBY)this.balloon.setPosition(a,b),this.prevBX=a,this.prevBY=b,e&&this.balloon.showBalloon(e)}},adjustBalloonCoordinate:function(a){return a},createBalloon:function(){var a=this.chart,b=a.chartCursor;b&&(b=b.cursorPosition,"mouse"!=b&&(this.stickBalloonToCategory=!0),"start"==b&&(this.stickBalloonToStart=!0),"ValueAxis"==this.cname&&(this.stickBalloonToCategory=!1));this.balloon&&(this.balloon.destroy&&this.balloon.destroy(),d.extend(this.balloon,a.balloon,!0))},setBalloonBounds:function(){var a= +this.balloon;if(a){var b=this.chart;a.cornerRadius=0;a.shadowAlpha=0;a.borderThickness=1;a.borderAlpha=1;a.adjustBorderColor=!1;a.showBullet=!1;this.balloon=a;a.chart=b;a.mainSet=b.plotBalloonsSet;a.pointerWidth=this.tickLength;if(this.parseDates||"date"==this.type)a.pointerWidth=0;a.className=this.id;b="V";"V"==this.orientation&&(b="H");this.stickBalloonToCategory||(a.animationDuration=0);var c,e,d,f,g=this.inside,k=this.width,l=this.height;switch(this.position){case "bottom":c=0;e=k;g?(d=0,f=l): +(d=l,f=l+1E3);break;case "top":c=0;e=k;g?(d=0,f=l):(d=-1E3,f=0);break;case "left":d=0;f=l;g?(c=0,e=k):(c=-1E3,e=0);break;case "right":d=0,f=l,g?(c=0,e=k):(c=k,e=k+1E3)}a.drop||(a.pointerOrientation=b);a.setBounds(c,d,e,f)}}})})();(function(){var d=window.AmCharts;d.ValueAxis=d.Class({inherits:d.AxisBase,construct:function(a){this.cname="ValueAxis";this.createEvents("axisChanged","logarithmicAxisFailed","axisZoomed","axisIntZoomed");d.ValueAxis.base.construct.call(this,a);this.dataChanged=!0;this.stackType="none";this.position="left";this.unitPosition="right";this.includeAllValues=this.recalculateToPercents=this.includeHidden=this.includeGuidesInMinMax=this.integersOnly=!1;this.durationUnits={DD:"d. ",hh:":",mm:":",ss:""}; +this.scrollbar=!1;this.baseValue=0;this.radarCategoriesEnabled=!0;this.axisFrequency=1;this.gridType="polygons";this.useScientificNotation=!1;this.axisTitleOffset=10;this.pointPosition="axis";this.minMaxMultiplier=1;this.logGridLimit=2;this.totalTextOffset=this.treatZeroAs=0;this.minPeriod="ss";this.relativeStart=0;this.relativeEnd=1;d.applyTheme(this,a,this.cname)},updateData:function(){0>=this.gridCountR&&(this.gridCountR=1);this.totals=[];this.data=this.chart.chartData;var a=this.chart;"xy"!=a.type&& +(this.stackGraphs("smoothedLine"),this.stackGraphs("line"),this.stackGraphs("column"),this.stackGraphs("step"));this.recalculateToPercents&&this.recalculate();this.synchronizationMultiplier&&this.synchronizeWith?(d.isString(this.synchronizeWith)&&(this.synchronizeWith=a.getValueAxisById(this.synchronizeWith)),this.synchronizeWith&&(this.synchronizeWithAxis(this.synchronizeWith),this.foundGraphs=!0)):(this.foundGraphs=!1,this.getMinMax(),0===this.start&&this.end==this.data.length-1&&isNaN(this.minZoom)&& +isNaN(this.maxZoom)&&(this.fullMin=this.min,this.fullMax=this.max,"date"!=this.type&&(isNaN(this.minimum)||(this.fullMin=this.minimum),isNaN(this.maximum)||(this.fullMax=this.maximum)),this.logarithmic&&(this.fullMin=this.logMin,0===this.fullMin&&(this.fullMin=this.treatZeroAs)),"date"==this.type&&(this.minimumDate||(this.fullMin=this.minRR),this.maximumDate||(this.fullMax=this.maxRR))))},draw:function(){d.ValueAxis.base.draw.call(this);var a=this.chart,b=this.set;this.labelRotationR=this.labelRotation; +d.setCN(a,this.set,"value-axis value-axis-"+this.id);d.setCN(a,this.labelsSet,"value-axis value-axis-"+this.id);d.setCN(a,this.axisLine.axisSet,"value-axis value-axis-"+this.id);var c=this.type;"duration"==c&&(this.duration="ss");!0===this.dataChanged&&(this.updateData(),this.dataChanged=!1);"date"==c&&(this.logarithmic=!1,this.min=this.minRR,this.max=this.maxRR,this.reversed=!1,this.getDateMinMax());if(this.logarithmic){var e=this.treatZeroAs,h=this.getExtremes(0,this.data.length-1).min;!isNaN(this.minimum)&& +this.minimum=h||0>=this.minimum){this.fire({type:"logarithmicAxisFailed",chart:a});return}}this.grid0=null;var f,g,k=a.dx,l=a.dy,e=!1,h=this.logarithmic;if(isNaN(this.min)||isNaN(this.max)||!this.foundGraphs||Infinity==this.min||-Infinity==this.max)e=!0;else{"date"==this.type&&this.min==this.max&&(this.max+=this.minDuration(),this.min-=this.minDuration());var m= +this.labelFrequency,n=this.showFirstLabel,q=this.showLastLabel,p=1,t=0;this.minCalc=this.min;this.maxCalc=this.max;if(this.strictMinMax&&(isNaN(this.minimum)||(this.min=this.minimum),isNaN(this.maximum)||(this.max=this.maximum),this.min==this.max))return;isNaN(this.minZoom)||(this.minReal=this.min=this.minZoom);isNaN(this.maxZoom)||(this.max=this.maxZoom);if(this.logarithmic){g=Math.log(this.fullMax)*Math.LOG10E-Math.log(this.fullMin)*Math.LOG10E;var r=Math.log(this.max)/Math.LN10-Math.log(this.fullMin)* +Math.LOG10E;this.relativeStart=(Math.log(this.minReal)/Math.LN10-Math.log(this.fullMin)*Math.LOG10E)/g;this.relativeEnd=r/g}else this.relativeStart=d.fitToBounds((this.min-this.fullMin)/(this.fullMax-this.fullMin),0,1),this.relativeEnd=d.fitToBounds((this.max-this.fullMin)/(this.fullMax-this.fullMin),0,1);var r=Math.round((this.maxCalc-this.minCalc)/this.step)+1,v;!0===h?(v=Math.log(this.max)*Math.LOG10E-Math.log(this.minReal)*Math.LOG10E,this.stepWidth=this.axisWidth/v,v>this.logGridLimit&&(r=Math.ceil(Math.log(this.max)* +Math.LOG10E)+1,t=Math.round(Math.log(this.minReal)*Math.LOG10E),r>this.gridCountR&&(p=Math.ceil(r/this.gridCountR)))):this.stepWidth=this.axisWidth/(this.max-this.min);var y=0;1>this.step&&-1this.maxDecCount&&(y=this.maxDecCount);var x=this.precision;isNaN(x)||(y=x);isNaN(this.maxZoom)&&(this.max=d.roundTo(this.max,this.maxDecCount),this.min=d.roundTo(this.min,this.maxDecCount));g={};g.precision=y;g.decimalSeparator=a.nf.decimalSeparator; +g.thousandsSeparator=a.nf.thousandsSeparator;this.numberFormatter=g;var u;this.exponential=!1;for(g=t;g=this.autoRotateCount&&!isNaN(this.autoRotateAngle)&&(this.labelRotationR=this.autoRotateAngle),c=this.minCalc,h&&(r++,c=this.maxCalc-r*y),this.gridCountReal=r,g=this.startCount=t;gthis.logGridLimit)t=Math.pow(10,g);else if(0>=t&&(t=c+y*g+y/2,0>=t))continue; +u=this.formatValue(t,!1,g);Math.round(g/m)!=g/m&&(u=void 0);if(0===g&&!n||g==r-1&&!q)u=" ";f=this.getCoordinate(t);var B;this.rotate&&this.autoWrap&&(B=this.stepWidth*y-10);u=new this.axisItemRenderer(this,f,u,void 0,B,void 0,void 0,this.boldLabels);this.pushAxisItem(u);if(t==this.baseValue&&"radar"!=a.type){var D,C;u=this.width;var J=this.height;"H"==this.orientation?0<=f&&f<=u+1&&(D=[f,f,f+k],C=[J,0,l]):0<=f&&f<=J+1&&(D=[0,u,u+k],C=[f,f,f+l]);D&&(u=d.fitToBounds(2*this.gridAlpha,0,1),isNaN(this.zeroGridAlpha)|| +(u=this.zeroGridAlpha),u=d.line(a.container,D,C,this.gridColor,u,1,this.dashLength),u.translate(this.x,this.y),this.grid0=u,a.axesSet.push(u),u.toBack(),d.setCN(a,u,this.bcn+"zero-grid-"+this.id),d.setCN(a,u,this.bcn+"zero-grid"))}if(!isNaN(z)&&0this.logGridLimit&&(z=Math.pow(10,g+p)),u=9,z=(z-t)/u);f=this.gridAlpha;this.gridAlpha=this.minorGridAlpha;for(J=1;Jl&&0>k||(k=new this.guideFillRenderer(this, +l,k,C),this.pushAxisItem(k,z),z=k.graphics(),C.graphics=z,C.balloonText&&this.addEventListeners(z,C));this.fillAlpha=D}g=this.baseValue;this.min>this.baseValue&&this.max>this.baseValue&&(g=this.min);this.minc&&(f.precision=Math.abs(c)),b&&1b&&c.shift();for(var e=Math.floor(Math.log(Math.abs(a))*Math.LOG10E),d=0;da){if(g=Math.pow(10,-g)*f,g==Math.round(g))return f}else if(f== +Math.round(f))return f}},stackGraphs:function(a){var b=this.stackType;"stacked"==b&&(b="regular");"line"==b&&(b="none");"100% stacked"==b&&(b="100%");this.stackType=b;var c=[],e=[],h=[],f=[],g,k=this.chart.graphs,l,m,n,q,p,t=this.baseValue,r=!1;if("line"==a||"step"==a||"smoothedLine"==a)r=!0;if(r&&("regular"==b||"100%"==b))for(q=0;qg?(m.values.close=g,isNaN(e[p])?m.values.open=t:(m.values.close+=e[p],m.values.open=e[p]),e[p]=m.values.close):(m.values.close=g,isNaN(h[p])?m.values.open=t:(m.values.close+=h[p],m.values.open=h[p]),h[p]=m.values.close)))}}for(p= +this.start;p<=this.end;p++)for(q=0;qc?(m.values.close=d.fitToBounds(c+e[p],-100,100),m.values.open=e[p],e[p]=m.values.close):(m.values.close=d.fitToBounds(c+ +h[p],-100,100),m.values.open=h[p],h[p]=m.values.close)))))},recalculate:function(){var a=this.chart,b=a.graphs,c;for(c=0;cq&&g++}if(m=a.recalculateFromDate)m=d.getDate(m,a.dataDateFormat,"fff"),g=a.getClosestIndex(a.chartData,"time",m.getTime(),!0,0,a.chartData.length),k=a.chartData.length-1;for(m=g;m<=k&&(g=this.data[m].axes[this.id].graphs[e.id],f=g.values[h],e.recalculateValue&&(f=g.dataContext[e.valueField+e.recalculateValue]),isNaN(f));m++);this.recBaseValue=f;for(h=l;h<=k;h++){g=this.data[h].axes[this.id].graphs[e.id];g.percents={};var l= +g.values,p;for(p in l)g.percents[p]="percents"!=p?l[p]/f*100-100:l[p]}}}},getMinMax:function(){var a=!1,b=this.chart,c=b.graphs,e;for(e=0;ethis.max&&(this.max=c.toValue),c.value>this.max&&(this.max=c.value);isNaN(this.minimum)||(this.min=this.minimum);isNaN(this.maximum)||(this.max=this.maximum);"date"==this.type&&this.getDateMinMax(); +this.min>this.max&&(a=this.max,this.max=this.min,this.min=a);isNaN(this.minZoom)||(this.min=this.minZoom);isNaN(this.maxZoom)||(this.max=this.maxZoom);this.minCalc=this.min;this.maxCalc=this.max;this.minReal=this.min;this.maxReal=this.max;0===this.min&&0===this.max&&(this.max=9);this.min>this.max&&(this.min=this.max-1);a=this.min;b=this.max;c=this.max-this.min;e=0===c?Math.pow(10,Math.floor(Math.log(Math.abs(this.max))*Math.LOG10E))/10:Math.pow(10,Math.floor(Math.log(Math.abs(c))*Math.LOG10E))/10; +isNaN(this.maximum)&&(this.max=Math.ceil(this.max/e)*e+e);isNaN(this.minimum)&&(this.min=Math.floor(this.min/e)*e-e);0>this.min&&0<=a&&(this.min=0);0=b&&(this.max=0);"100%"==this.stackType&&(this.min=0>this.min?-100:0,this.max=0>this.max?0:100);c=this.max-this.min;e=Math.pow(10,Math.floor(Math.log(Math.abs(c))*Math.LOG10E))/10;this.step=Math.ceil(c/this.gridCountR/e)*e;c=Math.pow(10,Math.floor(Math.log(Math.abs(this.step))*Math.LOG10E));c=d.fixStepE(c);e=Math.ceil(this.step/c);5=e&&2c?(this.maxDecCount=Math.abs(Math.log(Math.abs(c))*Math.LOG10E),this.maxDecCount=Math.round(this.maxDecCount),this.step=d.roundTo(this.step,this.maxDecCount+1)):this.maxDecCount=0;this.min=this.step*Math.floor(this.min/this.step);this.max=this.step*Math.ceil(this.max/this.step);0>this.min&&0<=a&&(this.min=0);0=b&&(this.max=0);1e&&(e=l);else for(var m in k)k.hasOwnProperty(m)&&"percents"!=m&&"total"!=m&&"error"!=m&&(l=k[m],le&&(e=l))}}}return{min:c,max:e}},zoomOut:function(a){this.maxZoom=this.minZoom=NaN;this.zoomToRelativeValues(0,1,a)},zoomToRelativeValues:function(a,b,c){if(this.reversed){var e=a;a=1-b;b=1-e}var d=this.fullMax,e=this.fullMin,f=e+(d-e)*a,g=e+(d-e)*b;this.logarithmic&&(d=Math.log(d)*Math.LOG10E-Math.log(e)*Math.LOG10E,f=Math.pow(10, +d*a+Math.log(e)*Math.LOG10E),g=Math.pow(10,d*b+Math.log(e)*Math.LOG10E));return this.zoomToValues(f,g,c)},zoomToValues:function(a,b,c){if(bn?(w=X+ia*Math.sin(T)-B-3+2,G+=-ia*Math.cos(T)-Pa*Math.sin(T)-4):w-=B+r+3+3,w-=aa):(0n?(w=X+B+3-ia/2*Math.sin(T)+2,G+=ia/2*Math.cos(T)):w+=B+u+3+3,w+=aa)):(w+=ma+r/2-ea,G+=va,I?(0Aa+2||0>r))ca.remove(),ca=null}else{0<=b&&b<=X+1&&(0X+1||wc&&"object"==typeof n&&(n=n.join(",").split(",").reverse());"V"==g?(g=d.rect(l,a.width,c,n,m),g.translate(h,b-k+f)):(g=d.rect(l, +c,a.height,n,m),g.translate(b-k+h,f));d.setCN(a.chart,g,"guide-fill");e.id&&d.setCN(a.chart,g,"guide-fill-"+e.id);this.set=l.set([g])},graphics:function(){return this.set},getLabel:function(){}})})();(function(){var d=window.AmCharts;d.AmChart=d.Class({construct:function(a){this.svgIcons=this.tapToActivate=!0;this.theme=a;this.classNamePrefix="amcharts";this.addClassNames=!1;this.version="3.20.12";d.addChart(this);this.createEvents("buildStarted","dataUpdated","init","rendered","drawn","failed","resized","animationFinished");this.height=this.width="100%";this.dataChanged=!0;this.chartCreated=!1;this.previousWidth=this.previousHeight=0;this.backgroundColor="#FFFFFF";this.borderAlpha=this.backgroundAlpha= +0;this.color=this.borderColor="#000000";this.fontFamily="Verdana";this.fontSize=11;this.usePrefixes=!1;this.autoResize=!0;this.autoDisplay=!1;this.addCodeCredits=this.accessible=!0;this.touchStartTime=this.touchClickDuration=0;this.precision=-1;this.percentPrecision=2;this.decimalSeparator=".";this.thousandsSeparator=",";this.labels=[];this.allLabels=[];this.titles=[];this.marginRight=this.marginLeft=this.autoMarginOffset=0;this.timeOuts=[];this.creditsPosition="top-left";var b=document.createElement("div"), +c=b.style;c.overflow="hidden";c.position="relative";c.textAlign="left";this.chartDiv=b;b=document.createElement("div");c=b.style;c.overflow="hidden";c.position="relative";c.textAlign="left";this.legendDiv=b;this.titleHeight=0;this.hideBalloonTime=150;this.handDrawScatter=2;this.cssScale=this.handDrawThickness=1;this.cssAngle=0;this.prefixesOfBigNumbers=[{number:1E3,prefix:"k"},{number:1E6,prefix:"M"},{number:1E9,prefix:"G"},{number:1E12,prefix:"T"},{number:1E15,prefix:"P"},{number:1E18,prefix:"E"}, +{number:1E21,prefix:"Z"},{number:1E24,prefix:"Y"}];this.prefixesOfSmallNumbers=[{number:1E-24,prefix:"y"},{number:1E-21,prefix:"z"},{number:1E-18,prefix:"a"},{number:1E-15,prefix:"f"},{number:1E-12,prefix:"p"},{number:1E-9,prefix:"n"},{number:1E-6,prefix:"\u03bc"},{number:.001,prefix:"m"}];this.panEventsEnabled=!0;this.product="amcharts";this.animations=[];this.balloon=new d.AmBalloon(this.theme);this.balloon.chart=this;this.processTimeout=0;this.processCount=1E3;this.animatable=[];d.applyTheme(this, +a,"AmChart")},drawChart:function(){0a||isNaN(a))a=0;this.chartDiv.style.height=a+"px"}}return a},updateWidth:function(){var a=this.divRealWidth,b=this.divRealHeight,c=this.legend;if(c){var e=this.legendDiv,d=e.offsetWidth; +isNaN(c.width)||(d=c.width);c.ieW&&(d=c.ieW);var f=e.offsetHeight,e=e.style,g=this.chartDiv.style,c=c.position;if("right"==c||"left"==c){a-=d;if(0>a||isNaN(a))a=0;g.width=a+"px";this.balloon.setBounds(2,2,a-2,this.realHeight);"left"==c?(g.left=d+"px",e.left="0px"):(g.left="0px",e.left=a+"px");b>f&&(e.top=(b-f)/2+"px")}}return a},getTitleHeight:function(){this.drawTitles(!0);return this.titleHeight},addTitle:function(a,b,c,e,d){isNaN(b)&&(b=this.fontSize+2);a={text:a,size:b,color:c,alpha:e,bold:d, +enabled:!0};this.titles.push(a);return a},handleWheel:function(a){var b=0;a||(a=window.event);a.wheelDelta?b=a.wheelDelta/120:a.detail&&(b=-a.detail/3);b&&this.handleWheelReal(b,a.shiftKey);a.preventDefault&&a.preventDefault()},handleWheelReal:function(){},handleDocTouchStart:function(){this.hideBalloonReal();this.handleMouseMove();this.tmx=this.mouseX;this.tmy=this.mouseY;this.touchStartTime=(new Date).getTime()},handleDocTouchEnd:function(){-.5Math.abs(this.mouseX-this.tmx)&&4>Math.abs(this.mouseY-this.tmy)?(this.tapped=!0,this.panRequired&&this.panEventsEnabled&&this.chartDiv&&(this.chartDiv.style.msTouchAction="none",this.chartDiv.style.touchAction="none")):this.mouseIsOver||this.resetTouchStyle()):(this.tapped=!1,this.resetTouchStyle())},resetTouchStyle:function(){this.panEventsEnabled&&this.chartDiv&&(this.chartDiv.style.msTouchAction="auto",this.chartDiv.style.touchAction="auto")}, +checkTouchDuration:function(a){var b=this,c=(new Date).getTime();if(a)if(a.touches)b.isTouchEvent=!0;else if(!b.isTouchEvent)return!0;if(c-b.touchStartTime>b.touchClickDuration)return!0;setTimeout(function(){b.resetTouchDuration()},300)},resetTouchDuration:function(){this.isTouchEvent=!1},checkTouchMoved:function(){if(4a.valueAxis.minMaxMultiplier&&a.positiveClip(a.set));break;case "radar":a.createRadarGraph();break;case "xy":a.createXYGraph()}a.playedTO=setTimeout(function(){a.setAnimationPlayed.call(a)},500*a.chart.startDuration)}},setAnimationPlayed:function(){this.animationPlayed= +!0},createXYGraph:function(){var a=[],b=[],c=this.xAxis,e=this.yAxis;this.pmh=e.height;this.pmw=c.width;this.pmy=this.pmx=0;var d;for(d=this.start;d<=this.end;d++){var f=this.data[d].axes[c.id].graphs[this.id],g=f.values,k=g.x,l=g.y,g=c.getCoordinate(k,this.noRounding),m=e.getCoordinate(l,this.noRounding);if(!isNaN(k)&&!isNaN(l)&&(a.push(g),b.push(m),f.x=g,f.y=m,k=this.createBullet(f,g,m,d),l=this.labelText)){var l=this.createLabel(f,l),n=0;k&&(n=k.size);this.positionLabel(f,g,m,l,n)}}this.drawLineGraph(a, +b);this.launchAnimation()},createRadarGraph:function(){var a=this.valueAxis.stackType,b=[],c=[],e=[],d=[],f,g,k,l,m;for(m=this.start;m<=this.end;m++){var n=this.data[m].axes[this.valueAxis.id].graphs[this.id],q,p;"none"==a||"3d"==a?q=n.values.value:(q=n.values.close,p=n.values.open);if(isNaN(q))this.connect||(this.drawLineGraph(b,c,e,d),b=[],c=[],e=[],d=[]);else{var t=this.valueAxis.getCoordinate(q,this.noRounding)-this.height,t=t*this.valueAxis.rMultiplier,r=-360/(this.end-this.start+1)*m;"middle"== +this.valueAxis.pointPosition&&(r-=180/(this.end-this.start+1));q=t*Math.sin(r/180*Math.PI);t*=Math.cos(r/180*Math.PI);b.push(q);c.push(t);if(!isNaN(p)){var v=this.valueAxis.getCoordinate(p,this.noRounding)-this.height,v=v*this.valueAxis.rMultiplier,y=v*Math.sin(r/180*Math.PI),r=v*Math.cos(r/180*Math.PI);e.push(y);d.push(r);isNaN(k)&&(k=y);isNaN(l)&&(l=r)}r=this.createBullet(n,q,t,m);n.x=q;n.y=t;if(y=this.labelText)y=this.createLabel(n,y),v=0,r&&(v=r.size),this.positionLabel(n,q,t,y,v);isNaN(f)&&(f= +q);isNaN(g)&&(g=t)}}b.push(f);c.push(g);isNaN(k)||(e.push(k),d.push(l));this.drawLineGraph(b,c,e,d);this.launchAnimation()},positionLabel:function(a,b,c,e,d){if(e){var f=this.chart,g=this.valueAxis,k="middle",l=!1,m=this.labelPosition,n=e.getBBox(),q=this.chart.rotate,p=a.isNegative,t=this.fontSize;void 0===t&&(t=this.chart.fontSize);c-=n.height/2-t/2-1;void 0!==a.labelIsNegative&&(p=a.labelIsNegative);switch(m){case "right":m=q?p?"left":"right":"right";break;case "top":m=q?"top":p?"bottom":"top"; +break;case "bottom":m=q?"bottom":p?"top":"bottom";break;case "left":m=q?p?"right":"left":"left"}var t=a.columnGraphics,r=0,v=0;t&&(r=t.x,v=t.y);var y=this.labelOffset;switch(m){case "right":k="start";b+=d/2+y;break;case "top":c=g.reversed?c+(d/2+n.height/2+y):c-(d/2+n.height/2+y);break;case "bottom":c=g.reversed?c-(d/2+n.height/2+y):c+(d/2+n.height/2+y);break;case "left":k="end";b-=d/2+y;break;case "inside":"column"==this.type&&(l=!0,q?p?(k="end",b=r-3-y):(k="start",b=r+3+y):c=p?v+7+y:v-10-y);break; +case "middle":"column"==this.type&&(l=!0,q?b-=(b-r)/2+y-3:c-=(c-v)/2+y-3)}"auto"!=this.labelAnchor&&(k=this.labelAnchor);e.attr({"text-anchor":k});this.labelRotation&&e.rotate(this.labelRotation);e.translate(b,c);!this.showAllValueLabels&&t&&l&&(n=e.getBBox(),n.height>a.columnHeight||n.width>a.columnWidth)&&(e.remove(),e=null);if(e&&"radar"!=f.type)if(q){if(0>c||c>this.height)e.remove(),e=null;!this.showAllValueLabels&&(0>b||b>this.width)&&(e.remove(),e=null)}else{if(0>b||b>this.width)e.remove(), +e=null;!this.showAllValueLabels&&e&&(0>c||c>this.height)&&(e.remove(),e=null)}e&&this.allBullets.push(e);return e}},getGradRotation:function(){var a=270;"horizontal"==this.gradientOrientation&&(a=0);return this.gradientRotation=a},createSerialGraph:function(){this.dashLengthSwitched=this.fillColorsSwitched=this.lineColorSwitched=void 0;var a=this.chart,b=this.id,c=this.index,e=this.data,h=this.chart.container,f=this.valueAxis,g=this.type,k=this.columnWidthReal,l=this.showBulletsAt;isNaN(this.columnWidth)|| +(k=this.columnWidth);isNaN(k)&&(k=.8);var m=this.useNegativeColorIfDown,n=this.width,q=this.height,p=this.y,t=this.rotate,r=this.columnCount,v=d.toCoordinate(this.cornerRadiusTop,k/2),y=this.connect,x=[],u=[],A,z,B,D,C=this.chart.graphs.length,J,H=this.dx/this.tcc,S=this.dy/this.tcc,O=f.stackType,Q=this.start,ga=this.end,I=this.scrollbar,aa="graph-column-";I&&(aa="scrollbar-graph-column-");var va=this.categoryAxis,ma=this.baseCoord,Oa=this.negativeBase,Z=this.columnIndex,da=this.lineThickness,X=this.lineAlpha, +Aa=this.lineColorR,ea=this.dashLength,fa=this.set,Ba,ha=this.getGradRotation(),T=this.chart.columnSpacing,Y=va.cellWidth,Da=(Y*k-r)/r;T>Da&&(T=Da);var G,w,na,ia=q,Pa=n,ca=0,tb=0,ub,vb,gb,hb,wb=this.fillColorsR,Qa=this.negativeFillColors,Ja=this.negativeLineColor,Ya=this.fillAlphas,Za=this.negativeFillAlphas;"object"==typeof Ya&&(Ya=Ya[0]);"object"==typeof Za&&(Za=Za[0]);var xb=this.noRounding;"step"==g&&(xb=!1);var ib=f.getCoordinate(f.min);f.logarithmic&&(ib=f.getCoordinate(f.minReal));this.minCoord= +ib;this.resetBullet&&(this.bullet="none");if(!(I||"line"!=g&&"smoothedLine"!=g&&"step"!=g||(1==e.length&&"step"!=g&&"none"==this.bullet&&(this.bullet="round",this.resetBullet=!0),!Qa&&void 0==Ja||m))){var Ua=Oa;Ua>f.max&&(Ua=f.max);Uak&&(k=1);var Mb=this.fixedColumnWidth;isNaN(Mb)||(k=Mb);var L;if("line"==g||"step"==g||"smoothedLine"==g){if(0W?!0:!1);if(!I)switch(this.showBalloonAt){case "close":w.y=F;break;case "open":w.y=M;break;case "high":w.y=sa;break;case "low":w.y=qa}var ja=G.x[va.id],Wa=this.periodSpan-1;"step"!=g||isNaN(G.cellWidth)|| +(Y=G.cellWidth);var ya=Math.floor(Y/2)+Math.floor(Wa*Y/2),Ha=ya,nb=0;"left"==this.stepDirection&&(nb=(2*Y+Wa*Y)/2,ja-=nb);"center"==this.stepDirection&&(nb=Y/2,ja-=nb);"start"==this.pointPosition&&(ja-=Y/2+Math.floor(Wa*Y/2),ya=0,Ha=Math.floor(Y)+Math.floor(Wa*Y));"end"==this.pointPosition&&(ja+=Y/2+Math.floor(Wa*Y/2),ya=Math.floor(Y)+Math.floor(Wa*Y),Ha=0);if(Nb){var Cb=this.columnWidth;isNaN(Cb)||(ya*=Cb,Ha*=Cb)}I||(w.x=ja);-1E5>ja&&(ja=-1E5);ja>n+1E5&&(ja=n+1E5);t?(E=F,N=M,M=F=ja,isNaN(ta)&&!this.fillToGraph&& +(N=ma),pa=qa,ra=sa):(N=E=ja,isNaN(ta)&&!this.fillToGraph&&(M=ma));if(!Bb&&WTa?(Sa&&($a=!0),Sa=!1):(Sa||($a=!0),Sa=!0):w.isNegative=W=lb||Math.abs(F-kb)>=lb)x.push(E), +u.push(F),jb=E,kb=F;wa=E;Ea=F;ka=E;la=F;!Ra||isNaN(M)||isNaN(N)||(U.push(N),V.push(M));if($a||void 0!=w.lineColor&&w.lineColor!=this.lineColorSwitched||void 0!=w.fillColors&&w.fillColors!=this.fillColorsSwitched||!isNaN(w.dashLength))this.drawLineGraph(x,u,U,V),x=[E],u=[F],U=[],V=[],!Ra||isNaN(M)||isNaN(N)||(U.push(N),V.push(M)),m?Sa?(this.lineColorSwitched=Aa,this.fillColorsSwitched=wb):(this.lineColorSwitched=Ja,this.fillColorsSwitched=Qa):(this.lineColorSwitched=w.lineColor,this.fillColorsSwitched= +w.fillColors),this.dashLengthSwitched=w.dashLength;w.gap&&(this.drawLineGraph(x,u,U,V),x=[],u=[],U=[],V=[])}break;case "smoothedLine":if(isNaN(W))y||(this.drawSmoothedGraph(x,u,U,V),x=[],u=[],U=[],V=[]);else{if(Math.abs(E-jb)>=lb||Math.abs(F-kb)>=lb)x.push(E),u.push(F),jb=E,kb=F;wa=E;Ea=F;ka=E;la=F;!Ra||isNaN(M)||isNaN(N)||(U.push(N),V.push(M));void 0==w.lineColor&&void 0==w.fillColors&&isNaN(w.dashLength)||(this.drawSmoothedGraph(x,u,U,V),x=[E],u=[F],U=[],V=[],!Ra||isNaN(M)||isNaN(N)||(U.push(N), +V.push(M)),this.lineColorSwitched=w.lineColor,this.fillColorsSwitched=w.fillColors,this.dashLengthSwitched=w.dashLength);w.gap&&(this.drawSmoothedGraph(x,u,U,V),x=[],u=[],U=[],V=[])}break;case "step":if(!isNaN(W)){t?(isNaN(A)||(x.push(A),u.push(F-ya)),u.push(F-ya),x.push(E),u.push(F+Ha),x.push(E),!Ra||isNaN(M)||isNaN(N)||(isNaN(B)||(U.push(B),V.push(M-ya)),U.push(N),V.push(M-ya),U.push(N),V.push(M+Ha))):(isNaN(z)||(u.push(z),x.push(E-ya)),x.push(E-ya),u.push(F),x.push(E+Ha),u.push(F),!Ra||isNaN(M)|| +isNaN(N)||(isNaN(D)||(U.push(N-ya),V.push(D)),U.push(N-ya),V.push(M),U.push(N+Ha),V.push(M)));A=E;z=F;B=N;D=M;wa=E;Ea=F;ka=E;la=F;if($a||void 0!=w.lineColor||void 0!=w.fillColors||!isNaN(w.dashLength)&&L=this.periodSpan||1ya+Ha)A=z=NaN;this.drawLineGraph(x,u,U,V);x=[];u=[];U=[];V=[]}break;case "column":Ca=Ga;void 0!=w.lineColor&&(Ca=w.lineColor);if(!isNaN(W)){m||(w.isNegative=WRb&&ob>Rb)){var za;if(t){"3d"==O?(P=F-(r/2-this.depthCount+1)*(k+T)+T/2+S*Z,R=N+H*Z,za=Z):(P=Math.floor(F-(r/2-Z)*(k+T)+T/2),R=N,za=0);K=k;wa=E;Ea=P+k/2;ka=E;la=P+k/2;P+K>q+za*S&&(K=q-P+za*S);Pba?!0:!1;0===ba&&1/W===1/-0&&(w.labelIsNegative=!0);isNaN(G.percentWidthValue)||(K=this.height*G.percentWidthValue/100,P=ja-K/2,Ea=P+K/2);K=d.roundTo(K,2);ba=d.roundTo(ba, +2);Pn+za*H&&(K=n-R+za*H);Rq&&(K=q-P);0>P&&(K+=P,P=0);if(Pta?(Db=[E,ra],Eb=[N,pa]): +(Db=[N,ra],Eb=[E,pa]);!isNaN(ra)&&!isNaN(pa)&&Fn&&(K=n-R);0>R&&(K+=R,R=0);ba=F-M;if(R=ta&&(Va=0);var ua=new d.Cuboid(h,K,ba,H,S,Ma,Va,da,Ca,X,ha,v,t,ea,bb,mb,aa),Fb,Gb;W>ta?(Fb=[F,sa],Gb=[M,qa]):(Fb=[M,sa],Gb=[F,qa]);!isNaN(sa)&&!isNaN(qa)&&EW?E-ac/2-2-fb-sb:E+ac/2+3+fb+ +sb):(db=wa,eb=0>W?F+bc/2+fb+sb:F-bc/2-3-fb-sb);Na.translate(db,eb);f.totals[L]=Na;t?(0>eb||eb>q)&&Na.remove():(0>db||db>n)&&Na.remove()}}}}}}}this.lastDataItem=w;if("line"==g||"step"==g||"smoothedLine"==g)"smoothedLine"==g?this.drawSmoothedGraph(x,u,U,V):this.drawLineGraph(x,u,U,V),I||this.launchAnimation();this.bulletsHidden&&this.hideBullets();this.customBulletsHidden&&this.hideCustomBullets()},animateColumns:function(a,b){var c=this,e=c.chart.startDuration;0h.height&&(z=h.height),0>z&&(z=0));q=d.line(l,a,b,t,q,p,x,!1,!0,f);q.node.setAttribute("stroke-linejoin","round");d.setCN(k,q,h.bcn+"stroke");m.push(q);m.click(function(a){h.handleGraphEvent(a,"clickGraph")}).mouseover(function(a){h.handleGraphEvent(a, +"rollOverGraph")}).mouseout(function(a){h.handleGraphEvent(a,"rollOutGraph")}).touchmove(function(a){h.chart.handleMouseMove(a)}).touchend(function(a){h.chart.handleTouchEnd(a)});void 0===y||h.useNegativeColorIfDown||(p=d.line(l,a,b,y,r,p,x,!1,!0,f),p.node.setAttribute("stroke-linejoin","round"),d.setCN(k,p,h.bcn+"stroke"),d.setCN(k,p,h.bcn+"stroke-negative"),n.push(p));if(0a&&(a=this.fillAlphas),0===a&&(a=this.bulletAlpha),0===a&&(a=1));return a},createBullet:function(a,b,c){if(!isNaN(b)&&!isNaN(c)&&("none"!=this.bullet||this.customBullet||a.bullet||a.customBullet)){var e=this.chart,h=this.container,f=this.bulletOffset,g=this.bulletSize;isNaN(a.bulletSize)|| +(g=a.bulletSize);var k=a.values.value,l=this.maxValue,m=this.minValue,n=this.maxBulletSize,q=this.minBulletSize;isNaN(l)||(isNaN(k)||(g=(k-m)/(l-m)*(n-q)+q),m==l&&(g=n));l=g;this.bulletAxis&&(g=a.values.error,isNaN(g)||(k=g),g=this.bulletAxis.stepWidth*k);gb||b>this.width||c<-g/2||c>this.height)p.remove(),p=null;p&&(this.bulletSet.push(p),p.translate(b,c),this.addListeners(p,a),this.allBullets.push(p));a.bx=b;a.by=c;d.setCN(e,p,this.bcn+"bullet");a.className&&d.setCN(e,p,a.className,!0)}if(p){p.size=g||0;if(e=this.bulletHitAreaSize)h=d.circle(h,e,"#FFFFFF",.001,0),h.translate(b,c),a.hitBullet=h,this.bulletSet.push(h),this.addListeners(h,a);a.bulletGraphics=p;void 0!==this.tabIndex&& +p.setAttr("tabindex",this.tabIndex)}else p={size:0};p.graphDataItem=a;return p}},showBullets:function(){var a=this.allBullets,b;this.bulletsHidden=!1;for(b=0;ba+k||hq+l)?(g.showBalloon(m),g.hide(0)):(g.followCursor(c),g.showBalloon(m)))):(this.hideBalloonReal(),g.hide(),this.resizeBullet(a,e,h))}else this.hideBalloonReal()}},resizeBullet:function(a,b,c){this.fixBulletSize();if(a&&d.isModern&&(1!=b||!isNaN(c))){var e=a.bulletGraphics;e&&!e.doNotScale&&(e.translate(a.bx,a.by,b),isNaN(c)||(e.setAttr("fill-opacity",c),e.setAttr("stroke-opacity", +c)),this.resizedDItem=a)}}})})();(function(){var d=window.AmCharts;d.ChartCursor=d.Class({construct:function(a){this.cname="ChartCursor";this.createEvents("changed","zoomed","onHideCursor","onShowCursor","draw","selected","moved","panning","zoomStarted");this.enabled=!0;this.cursorAlpha=1;this.selectionAlpha=.2;this.cursorColor="#CC0000";this.categoryBalloonAlpha=1;this.color="#FFFFFF";this.type="cursor";this.zoomed=!1;this.zoomable=!0;this.pan=!1;this.categoryBalloonDateFormat="MMM DD, YYYY";this.categoryBalloonText="[[category]]"; +this.categoryBalloonEnabled=this.valueBalloonsEnabled=!0;this.rolledOver=!1;this.cursorPosition="middle";this.bulletsEnabled=this.skipZoomDispatch=!1;this.bulletSize=8;this.selectWithoutZooming=this.oneBalloonOnly=!1;this.graphBulletSize=1.7;this.animationDuration=.3;this.zooming=!1;this.adjustment=0;this.avoidBalloonOverlapping=!0;this.leaveCursor=!1;this.leaveAfterTouch=!0;this.valueZoomable=!1;this.balloonPointerOrientation="horizontal";this.hLineEnabled=this.vLineEnabled=!0;this.vZoomEnabled= +this.hZoomEnabled=!1;d.applyTheme(this,a,this.cname)},draw:function(){this.destroy();var a=this.chart;a.panRequired=!0;var b=a.container;this.rotate=a.rotate;this.container=b;this.prevLineHeight=this.prevLineWidth=NaN;b=b.set();b.translate(this.x,this.y);this.set=b;a.cursorSet.push(b);this.createElements();d.isString(this.limitToGraph)&&(this.limitToGraph=d.getObjById(a.graphs,this.limitToGraph),this.fullWidth=!1,this.cursorPosition="middle");this.pointer=this.balloonPointerOrientation.substr(0,1).toUpperCase(); +this.isHidden=!1;this.hideLines();this.valueLineAxis||(this.valueLineAxis=a.valueAxes[0])},createElements:function(){var a=this,b=a.chart,c=b.dx,e=b.dy,h=a.width,f=a.height,g,k,l=a.cursorAlpha,m=a.valueLineAlpha;a.rotate?(g=m,k=l):(k=m,g=l);"xy"==b.type&&(k=l,void 0!==m&&(k=m),g=l);a.vvLine=d.line(a.container,[c,0,0],[e,0,f],a.cursorColor,g,1);d.setCN(b,a.vvLine,"cursor-line");d.setCN(b,a.vvLine,"cursor-line-vertical");a.hhLine=d.line(a.container,[0,h,h+c],[0,0,e],a.cursorColor,k,1);d.setCN(b,a.hhLine, +"cursor-line");d.setCN(b,a.hhLine,"cursor-line-horizontal");a.vLine=a.rotate?a.vvLine:a.hhLine;a.set.push(a.vvLine);a.set.push(a.hhLine);a.set.node.style.pointerEvents="none";a.fullLines=a.container.set();b=b.cursorLineSet;b.push(a.fullLines);b.translate(a.x,a.y);b.clipRect(-1,-1,h+2,f+2);void 0!==a.tabIndex&&(b.setAttr("tabindex",a.tabIndex),b.keyup(function(b){a.handleKeys(b)}).focus(function(b){a.showCursor()}).blur(function(b){a.hideCursor()}));a.set.clipRect(0,0,h,f)},handleKeys:function(a){var b= +this.prevIndex,c=this.chart;if(c){var e=c.chartData;e&&(isNaN(b)&&(b=e.length-1),37!=a.keyCode&&40!=a.keyCode||b--,39!=a.keyCode&&38!=a.keyCode||b++,b=d.fitToBounds(b,c.startIndex,c.endIndex),(a=this.chart.chartData[b])&&this.setPosition(a.x.categoryAxis),this.prevIndex=b)}},update:function(){var a=this.chart;if(a){var b=a.mouseX-this.x,c=a.mouseY-this.y;this.mouseX=b;this.mouseY=c;this.mouse2X=a.mouse2X-this.x;this.mouse2Y=a.mouse2Y-this.y;var e;if(a.chartData&&0document.documentMode&&(this.updateOnReleaseOnly=!0);this.dragIconHeight=this.dragIconWidth=35;this.dragIcon="dragIconRoundBig"; +this.dragCursorHover="cursor: cursor: grab; cursor:-moz-grab; cursor:-webkit-grab;";this.dragCursorDown="cursor: cursor: grab; cursor:-moz-grabbing; cursor:-webkit-grabbing;";this.enabled=!0;this.percentStart=this.offset=0;this.percentEnd=1;d.applyTheme(this,a,"SimpleChartScrollbar")},getPercents:function(){var a=this.getDBox(),b=a.x,c=a.y,e=a.width,a=a.height;this.rotate?(b=1-c/this.height,c=1-(c+a)/this.height):(c=b/this.width,b=(b+e)/this.width);this.percentStart=c;this.percentEnd=b},draw:function(){var a= +this;a.destroy();if(a.enabled){var b=a.chart.container,c=a.rotate,e=a.chart;e.panRequired=!0;var h=b.set();a.set=h;e.scrollbarsSet.push(h);var f,g;c?(f=a.scrollbarHeight,g=e.plotAreaHeight):(g=a.scrollbarHeight,f=e.plotAreaWidth);a.width=f;if((a.height=g)&&f){var k=d.rect(b,f,g,a.backgroundColor,a.backgroundAlpha,1,a.backgroundColor,a.backgroundAlpha);d.setCN(e,k,"scrollbar-bg");a.bg=k;h.push(k);k=d.rect(b,f,g,"#000",.005);h.push(k);a.invisibleBg=k;k.click(function(){a.handleBgClick()}).mouseover(function(){a.handleMouseOver()}).mouseout(function(){a.handleMouseOut()}).touchend(function(){a.handleBgClick()}); +k=d.rect(b,f,g,a.selectedBackgroundColor,a.selectedBackgroundAlpha);d.setCN(e,k,"scrollbar-bg-selected");a.selectedBG=k;h.push(k);f=d.rect(b,f,g,"#000",.005);a.dragger=f;h.push(f);f.mousedown(function(b){a.handleDragStart(b)}).mouseup(function(){a.handleDragStop()}).mouseover(function(){a.handleDraggerOver()}).mouseout(function(){a.handleMouseOut()}).touchstart(function(b){a.handleDragStart(b)}).touchend(function(){a.handleDragStop()});g=e.pathToImages;var l,k=a.dragIcon.replace(/\.[a-z]*$/i,""); +d.isAbsolute(k)&&(g="");c?(l=g+k+"H"+e.extension,g=a.dragIconWidth,c=a.dragIconHeight):(l=g+k+e.extension,c=a.dragIconWidth,g=a.dragIconHeight);k=b.image(l,0,0,c,g);d.setCN(e,k,"scrollbar-grip-left");l=b.image(l,0,0,c,g);d.setCN(e,l,"scrollbar-grip-right");var m=10,n=20;e.panEventsEnabled&&(m=25,n=a.scrollbarHeight);var q=d.rect(b,m,n,"#000",.005),p=d.rect(b,m,n,"#000",.005);p.translate(-(m-c)/2,-(n-g)/2);q.translate(-(m-c)/2,-(n-g)/2);c=b.set([k,p]);b=b.set([l,q]);a.iconLeft=c;h.push(a.iconLeft); +a.iconRight=b;h.push(b);e.makeAccessible(c,a.accessibleLabel);e.makeAccessible(b,a.accessibleLabel);e.makeAccessible(f,a.accessibleLabel);c.setAttr("role","menuitem");b.setAttr("role","menuitem");f.setAttr("role","menuitem");void 0!==a.tabIndex&&(c.setAttr("tabindex",a.tabIndex),c.keyup(function(b){a.handleKeys(b,1,0)}));void 0!==a.tabIndex&&(f.setAttr("tabindex",a.tabIndex),f.keyup(function(b){a.handleKeys(b,1,1)}));void 0!==a.tabIndex&&(b.setAttr("tabindex",a.tabIndex),b.keyup(function(b){a.handleKeys(b, +0,1)}));c.mousedown(function(){a.leftDragStart()}).mouseup(function(){a.leftDragStop()}).mouseover(function(){a.iconRollOver()}).mouseout(function(){a.iconRollOut()}).touchstart(function(){a.leftDragStart()}).touchend(function(){a.leftDragStop()});b.mousedown(function(){a.rightDragStart()}).mouseup(function(){a.rightDragStop()}).mouseover(function(){a.iconRollOver()}).mouseout(function(){a.iconRollOut()}).touchstart(function(){a.rightDragStart()}).touchend(function(){a.rightDragStop()});d.ifArray(e.chartData)? +h.show():h.hide();a.hideDragIcons();a.clipDragger(!1)}h.translate(a.x,a.y);h.node.style.msTouchAction="none";h.node.style.touchAction="none"}},handleKeys:function(a,b,c){this.getPercents();var e=this.percentStart,d=this.percentEnd;if(this.rotate)var f=d,d=e,e=f;if(37==a.keyCode||40==a.keyCode)e-=.02*b,d-=.02*c;if(39==a.keyCode||38==a.keyCode)e+=.02*b,d+=.02*c;this.rotate&&(a=d,d=e,e=a);isNaN(d)||isNaN(e)||this.percentZoom(e,d,!0)},updateScrollbarSize:function(a,b){if(!isNaN(a)&&!isNaN(b)){a=Math.round(a); +b=Math.round(b);var c=this.dragger,e,d,f,g,k;this.rotate?(e=0,d=a,f=this.width+1,g=b-a,c.setAttr("height",b-a),c.setAttr("y",d)):(e=a,d=0,f=b-a,g=this.height+1,k=b-a,c.setAttr("x",e),c.setAttr("width",k));this.clipAndUpdate(e,d,f,g)}},update:function(){var a,b=!1,c,e,d=this.x,f=this.y,g=this.dragger,k=this.getDBox();if(k){c=k.x+d;e=k.y+f;var l=k.width,k=k.height,m=this.rotate,n=this.chart,q=this.width,p=this.height,t=n.mouseX,r=n.mouseY;a=this.initialMouse;this.forceClip&&this.clipDragger(!0);if(n.mouseIsOver){this.dragging&& +(n=this.initialCoord,m?(a=n+(r-a),0>a&&(a=0),n=p-k,a>n&&(a=n),g.setAttr("y",a)):(a=n+(t-a),0>a&&(a=0),n=q-l,a>n&&(a=n),g.setAttr("x",a)),this.clipDragger(!0));if(this.resizingRight){if(m)if(a=r-e,!isNaN(this.maxHeight)&&a>this.maxHeight&&(a=this.maxHeight),a+e>p+f&&(a=p-e+f),0>a)this.resizingRight=!1,b=this.resizingLeft=!0;else{if(0===a||isNaN(a))a=.1;g.setAttr("height",a)}else if(a=t-c,!isNaN(this.maxWidth)&&a>this.maxWidth&&(a=this.maxWidth),a+c>q+d&&(a=q-c+d),0>a)this.resizingRight=!1,b=this.resizingLeft= +!0;else{if(0===a||isNaN(a))a=.1;g.setAttr("width",a)}this.clipDragger(!0)}if(this.resizingLeft){if(m)if(c=e,e=r,ep+f&&(e=p+f),a=!0===b?c-e:k+c-e,!isNaN(this.maxHeight)&&a>this.maxHeight&&(a=this.maxHeight,e=c),0>a)this.resizingRight=!0,this.resizingLeft=!1,g.setAttr("y",c+k-f);else{if(0===a||isNaN(a))a=.1;g.setAttr("y",e-f);g.setAttr("height",a)}else if(e=t,eq+d&&(e=q+d),a=!0===b?c-e:l+c-e,!isNaN(this.maxWidth)&&a>this.maxWidth&&(a=this.maxWidth, +e=c),0>a)this.resizingRight=!0,this.resizingLeft=!1,g.setAttr("x",c+l-d);else{if(0===a||isNaN(a))a=.1;g.setAttr("x",e-d);g.setAttr("width",a)}this.clipDragger(!0)}}}},stopForceClip:function(){this.animating=this.forceClip=!1},clipDragger:function(a){var b=this.getDBox();if(b){var c=b.x,e=b.y,d=b.width,b=b.height,f=!1;if(this.rotate){if(c=0,d=this.width+1,this.clipY!=e||this.clipH!=b)f=!0}else if(e=0,b=this.height+1,this.clipX!=c||this.clipW!=d)f=!0;f&&(this.clipAndUpdate(c,e,d,b),a&&(this.updateOnReleaseOnly|| +this.dispatchScrollbarEvent()))}},maskGraphs:function(){},clipAndUpdate:function(a,b,c,e){this.clipX=a;this.clipY=b;this.clipW=c;this.clipH=e;this.selectedBG.setAttr("width",c);this.selectedBG.setAttr("height",e);this.selectedBG.translate(a,b);this.updateDragIconPositions();this.maskGraphs(a,b,c,e)},dispatchScrollbarEvent:function(){if(this.skipEvent)this.skipEvent=!1;else{var a=this.chart;a.hideBalloon();var b=this.getDBox(),c=b.x,e=b.y,d=b.width,b=b.height;this.getPercents();this.rotate?(c=e,d= +this.height/b):d=this.width/d;this.fire({type:"zoomed",position:c,chart:a,target:this,multiplier:d,relativeStart:this.percentStart,relativeEnd:this.percentEnd})}},updateDragIconPositions:function(){var a=this.getDBox(),b=a.x,c=a.y,e=this.iconLeft,d=this.iconRight,f,g,k=this.scrollbarHeight;this.rotate?(f=this.dragIconWidth,g=this.dragIconHeight,e.translate((k-g)/2,c-f/2),d.translate((k-g)/2,c+a.height-f/2)):(f=this.dragIconHeight,g=this.dragIconWidth,e.translate(b-g/2,(k-f)/2),d.translate(b-g/2+a.width, +(k-f)/2))},showDragIcons:function(){this.resizeEnabled&&(this.iconLeft.show(),this.iconRight.show())},hideDragIcons:function(){if(!this.resizingLeft&&!this.resizingRight&&!this.dragging){if(this.hideResizeGrips||!this.resizeEnabled)this.iconLeft.hide(),this.iconRight.hide();this.removeCursors()}},removeCursors:function(){this.chart.setMouseCursor("auto")},fireZoomEvent:function(a){this.fire({type:a,chart:this.chart,target:this})},percentZoom:function(a,b,c){a=d.fitToBounds(a,0,b);b=d.fitToBounds(b, +a,1);if(this.dragger&&this.enabled){this.dragger.stop();isNaN(a)&&(a=0);isNaN(b)&&(b=1);var e,h;this.rotate?(e=this.height,b=e-e*b,h=e-e*a):(e=this.width,h=e*b,b=e*a);this.updateScrollbarSize(b,h);this.clipDragger(!1);this.getPercents();c&&this.dispatchScrollbarEvent()}},destroy:function(){this.clear();d.remove(this.set);d.remove(this.iconRight);d.remove(this.iconLeft)},clear:function(){},handleDragStart:function(){if(this.enabled){this.fireZoomEvent("zoomStarted");var a=this.chart;this.dragger.stop(); +this.removeCursors();d.isModern&&this.dragger.node.setAttribute("style",this.dragCursorDown);this.dragging=!0;var b=this.getDBox();this.rotate?(this.initialCoord=b.y,this.initialMouse=a.mouseY):(this.initialCoord=b.x,this.initialMouse=a.mouseX)}},handleDragStop:function(){this.updateOnReleaseOnly&&(this.update(),this.skipEvent=!1,this.dispatchScrollbarEvent());this.dragging=!1;this.mouseIsOver&&this.removeCursors();d.isModern&&this.dragger.node.setAttribute("style",this.dragCursorHover);this.update(); +this.fireZoomEvent("zoomEnded")},handleDraggerOver:function(){this.handleMouseOver();d.isModern&&this.dragger.node.setAttribute("style",this.dragCursorHover)},leftDragStart:function(){this.fireZoomEvent("zoomStarted");this.dragger.stop();this.resizingLeft=!0},leftDragStop:function(){this.resizingLeft&&(this.resizingLeft=!1,this.mouseIsOver||this.removeCursors(),this.updateOnRelease(),this.fireZoomEvent("zoomEnded"))},rightDragStart:function(){this.fireZoomEvent("zoomStarted");this.dragger.stop(); +this.resizingRight=!0},rightDragStop:function(){this.resizingRight&&(this.resizingRight=!1,this.mouseIsOver||this.removeCursors(),this.updateOnRelease(),this.fireZoomEvent("zoomEnded"))},iconRollOut:function(){this.removeCursors()},iconRollOver:function(){this.rotate?this.chart.setMouseCursor("ns-resize"):this.chart.setMouseCursor("ew-resize");this.handleMouseOver()},getDBox:function(){if(this.dragger)return this.dragger.getBBox()},handleBgClick:function(){var a=this;if(!a.resizingRight&&!a.resizingLeft){a.zooming= +!0;var b,c,e=a.scrollDuration,h=a.dragger;b=a.getDBox();var f=b.height,g=b.width;c=a.chart;var k=a.y,l=a.x,m=a.rotate;m?(b="y",c=c.mouseY-f/2-k,c=d.fitToBounds(c,0,a.height-f)):(b="x",c=c.mouseX-g/2-l,c=d.fitToBounds(c,0,a.width-g));a.updateOnReleaseOnly?(a.skipEvent=!1,h.setAttr(b,c),a.dispatchScrollbarEvent(),a.clipDragger()):(a.animating=!0,c=Math.round(c),m?h.animate({y:c},e,">"):h.animate({x:c},e,">"),a.forceClip=!0,clearTimeout(a.forceTO),a.forceTO=setTimeout(function(){a.stopForceClip.call(a)}, +5E3*e))}},updateOnRelease:function(){this.updateOnReleaseOnly&&(this.update(),this.skipEvent=!1,this.dispatchScrollbarEvent())},handleReleaseOutside:function(){if(this.set){if(this.resizingLeft||this.resizingRight||this.dragging)this.dragging=this.resizingRight=this.resizingLeft=!1,this.updateOnRelease(),this.removeCursors();this.animating=this.mouseIsOver=!1;this.hideDragIcons();this.update()}},handleMouseOver:function(){this.mouseIsOver=!0;this.showDragIcons()},handleMouseOut:function(){this.mouseIsOver= +!1;this.hideDragIcons();this.removeCursors()}})})();(function(){var d=window.AmCharts;d.ChartScrollbar=d.Class({inherits:d.SimpleChartScrollbar,construct:function(a){this.cname="ChartScrollbar";d.ChartScrollbar.base.construct.call(this,a);this.graphLineColor="#BBBBBB";this.graphLineAlpha=0;this.graphFillColor="#BBBBBB";this.graphFillAlpha=1;this.selectedGraphLineColor="#888888";this.selectedGraphLineAlpha=0;this.selectedGraphFillColor="#888888";this.selectedGraphFillAlpha=1;this.gridCount=0;this.gridColor="#FFFFFF";this.gridAlpha=.7;this.skipEvent= +this.autoGridCount=!1;this.color="#FFFFFF";this.scrollbarCreated=!1;this.oppositeAxis=!0;this.accessibleLabel="Zoom chart using cursor arrows";d.applyTheme(this,a,this.cname)},init:function(){var a=this.categoryAxis,b=this.chart,c=this.gridAxis;a||("CategoryAxis"==this.gridAxis.cname?(this.catScrollbar=!0,a=new d.CategoryAxis,a.id="scrollbar"):(a=new d.ValueAxis,a.data=b.chartData,a.id=c.id,a.type=c.type,a.maximumDate=c.maximumDate,a.minimumDate=c.minimumDate,a.minPeriod=c.minPeriod),this.categoryAxis= +a);a.chart=b;var e=b.categoryAxis;e&&(a.firstDayOfWeek=e.firstDayOfWeek);a.dateFormats=c.dateFormats;a.markPeriodChange=c.markPeriodChange;a.boldPeriodBeginning=c.boldPeriodBeginning;a.labelFunction=c.labelFunction;a.axisItemRenderer=d.RecItem;a.axisRenderer=d.RecAxis;a.guideFillRenderer=d.RecFill;a.inside=!0;a.fontSize=this.fontSize;a.tickLength=0;a.axisAlpha=0;d.isString(this.graph)&&(this.graph=d.getObjById(b.graphs,this.graph));(a=this.graph)&&this.catScrollbar&&(c=this.valueAxis,c||(this.valueAxis= +c=new d.ValueAxis,c.visible=!1,c.scrollbar=!0,c.axisItemRenderer=d.RecItem,c.axisRenderer=d.RecAxis,c.guideFillRenderer=d.RecFill,c.labelsEnabled=!1,c.chart=b),b=this.unselectedGraph,b||(b=new d.AmGraph,b.scrollbar=!0,this.unselectedGraph=b,b.negativeBase=a.negativeBase,b.noStepRisers=a.noStepRisers),b=this.selectedGraph,b||(b=new d.AmGraph,b.scrollbar=!0,this.selectedGraph=b,b.negativeBase=a.negativeBase,b.noStepRisers=a.noStepRisers));this.scrollbarCreated=!0},draw:function(){var a=this;d.ChartScrollbar.base.draw.call(a); +if(a.enabled){a.scrollbarCreated||a.init();var b=a.chart,c=b.chartData,e=a.categoryAxis,h=a.rotate,f=a.x,g=a.y,k=a.width,l=a.height,m=a.gridAxis,n=a.set;e.setOrientation(!h);e.parseDates=m.parseDates;"ValueAxis"==a.categoryAxis.cname&&(e.rotate=!h);e.equalSpacing=m.equalSpacing;e.minPeriod=m.minPeriod;e.startOnAxis=m.startOnAxis;e.width=k-1;e.height=l;e.gridCount=a.gridCount;e.gridColor=a.gridColor;e.gridAlpha=a.gridAlpha;e.color=a.color;e.tickLength=0;e.axisAlpha=0;e.autoGridCount=a.autoGridCount; +e.parseDates&&!e.equalSpacing&&e.timeZoom(b.firstTime,b.lastTime);e.minimum=a.gridAxis.fullMin;e.maximum=a.gridAxis.fullMax;e.strictMinMax=!0;e.zoom(0,c.length-1);if((m=a.graph)&&a.catScrollbar){var q=a.valueAxis,p=m.valueAxis;q.id=p.id;q.rotate=h;q.setOrientation(h);q.width=k;q.height=l;q.dataProvider=c;q.reversed=p.reversed;q.logarithmic=p.logarithmic;q.gridAlpha=0;q.axisAlpha=0;n.push(q.set);h?(q.y=g,q.x=0):(q.x=f,q.y=0);var f=Infinity,g=-Infinity,t;for(t=0;tg&&(g=y)}}Infinity!=f&&(q.minimum=f);-Infinity!=g&&(q.maximum=g+.1*(g-f));f==g&&(--q.minimum,q.maximum+=1);void 0!==a.minimum&&(q.minimum=a.minimum);void 0!==a.maximum&&(q.maximum=a.maximum);q.zoom(0,c.length-1);v=a.unselectedGraph;v.id=m.id;v.bcn="scrollbar-graph-";v.rotate=h;v.chart=b;v.data=c;v.valueAxis=q;v.chart=m.chart;v.categoryAxis=a.categoryAxis;v.periodSpan=m.periodSpan;v.valueField=m.valueField;v.openField= +m.openField;v.closeField=m.closeField;v.highField=m.highField;v.lowField=m.lowField;v.lineAlpha=a.graphLineAlpha;v.lineColorR=a.graphLineColor;v.fillAlphas=a.graphFillAlpha;v.fillColorsR=a.graphFillColor;v.connect=m.connect;v.hidden=m.hidden;v.width=k;v.height=l;v.pointPosition=m.pointPosition;v.stepDirection=m.stepDirection;v.periodSpan=m.periodSpan;p=a.selectedGraph;p.id=m.id;p.bcn=v.bcn+"selected-";p.rotate=h;p.chart=b;p.data=c;p.valueAxis=q;p.chart=m.chart;p.categoryAxis=e;p.periodSpan=m.periodSpan; +p.valueField=m.valueField;p.openField=m.openField;p.closeField=m.closeField;p.highField=m.highField;p.lowField=m.lowField;p.lineAlpha=a.selectedGraphLineAlpha;p.lineColorR=a.selectedGraphLineColor;p.fillAlphas=a.selectedGraphFillAlpha;p.fillColorsR=a.selectedGraphFillColor;p.connect=m.connect;p.hidden=m.hidden;p.width=k;p.height=l;p.pointPosition=m.pointPosition;p.stepDirection=m.stepDirection;p.periodSpan=m.periodSpan;b=a.graphType;b||(b=m.type);v.type=b;p.type=b;c=c.length-1;v.zoom(0,c);p.zoom(0, +c);p.set.click(function(){a.handleBackgroundClick()}).mouseover(function(){a.handleMouseOver()}).mouseout(function(){a.handleMouseOut()});v.set.click(function(){a.handleBackgroundClick()}).mouseover(function(){a.handleMouseOver()}).mouseout(function(){a.handleMouseOut()});n.push(v.set);n.push(p.set)}n.push(e.set);n.push(e.labelsSet);a.bg.toBack();a.invisibleBg.toFront();a.dragger.toFront();a.iconLeft.toFront();a.iconRight.toFront()}},timeZoom:function(a,b,c){this.startTime=a;this.endTime=b;this.timeDifference= +b-a;this.skipEvent=!d.toBoolean(c);this.zoomScrollbar();this.dispatchScrollbarEvent()},zoom:function(a,b){this.start=a;this.end=b;this.skipEvent=!0;this.zoomScrollbar()},dispatchScrollbarEvent:function(){if(this.categoryAxis&&"ValueAxis"==this.categoryAxis.cname)d.ChartScrollbar.base.dispatchScrollbarEvent.call(this);else if(this.skipEvent)this.skipEvent=!1;else{var a=this.chart.chartData,b,c,e=this.dragger.getBBox();b=e.x;var h=e.y,f=e.width,e=e.height,g=this.chart;this.rotate?(b=h,c=e):c=f;f={type:"zoomed", +target:this};f.chart=g;var k=this.categoryAxis,l=this.stepWidth,h=g.minSelectedTime,e=g.maxSelectedTime,m=!1;if(k.parseDates&&!k.equalSpacing){if(a=g.lastTime,g=g.firstTime,k=Math.round(b/l)+g,b=this.dragging?k+this.timeDifference:Math.round((b+c)/l)+g,k>b&&(k=b),0e&&(b=Math.round(k+(b-k)/2),m=Math.round(e/2),k=b-m,b+=m,m=!0),b>a&&(b=a),b-hb&&(b=k+h),k!=this.startTime||b!=this.endTime)this.startTime= +k,this.endTime=b,f.start=k,f.end=b,f.startDate=new Date(k),f.endDate=new Date(b),this.fire(f)}else{k.startOnAxis||(b+=l/2);c-=this.stepWidth/2;h=k.xToIndex(b);b=k.xToIndex(b+c);if(h!=this.start||this.end!=b)k.startOnAxis&&(this.resizingRight&&h==b&&b++,this.resizingLeft&&h==b&&(0this.timeDifference&&(this.timeDifference=0)},handleBackgroundClick:function(){d.ChartScrollbar.base.handleBackgroundClick.call(this);this.dragging||(this.difference=this.end-this.start,this.timeDifference=this.endTime-this.startTime,0>this.timeDifference&& +(this.timeDifference=0))}})})();(function(){var d=window.AmCharts;d.AmBalloon=d.Class({construct:function(a){this.cname="AmBalloon";this.enabled=!0;this.fillColor="#FFFFFF";this.fillAlpha=.8;this.borderThickness=2;this.borderColor="#FFFFFF";this.borderAlpha=1;this.cornerRadius=0;this.maxWidth=220;this.horizontalPadding=8;this.verticalPadding=4;this.pointerWidth=6;this.pointerOrientation="V";this.color="#000000";this.adjustBorderColor=!0;this.show=this.follow=this.showBullet=!1;this.bulletSize=3;this.shadowAlpha=.4;this.shadowColor= +"#000000";this.fadeOutDuration=this.animationDuration=.3;this.fixedPosition=!0;this.offsetY=6;this.offsetX=1;this.textAlign="center";this.disableMouseEvents=!0;this.deltaSignX=this.deltaSignY=1;d.isModern||(this.offsetY*=1.5);this.sdy=this.sdx=0;d.applyTheme(this,a,this.cname)},draw:function(){var a=this.pointToX,b=this.pointToY;d.isModern||(this.drop=!1);var c=this.chart;d.VML&&(this.fadeOutDuration=0);this.xAnim&&c.stopAnim(this.xAnim);this.yAnim&&c.stopAnim(this.yAnim);this.sdy=this.sdx=0;if(!isNaN(a)){var e= +this.follow,h=c.container,f=this.set;d.remove(f);this.removeDiv();f=h.set();f.node.style.pointerEvents="none";this.set=f;this.mainSet?(this.mainSet.push(this.set),this.sdx=this.mainSet.x,this.sdy=this.mainSet.y):c.balloonsSet.push(f);if(this.show){var g=this.l,k=this.t,l=this.r,m=this.b,n=this.balloonColor,q=this.fillColor,p=this.borderColor,t=q;void 0!=n&&(this.adjustBorderColor?t=p=n:q=n);var r=this.horizontalPadding,v=this.verticalPadding,y=this.pointerWidth,x=this.pointerOrientation,u=this.cornerRadius, +A=c.fontFamily,z=this.fontSize;void 0==z&&(z=c.fontSize);var n=document.createElement("div"),B=c.classNamePrefix;n.className=B+"-balloon-div";this.className&&(n.className=n.className+" "+B+"-balloon-div-"+this.className);B=n.style;this.disableMouseEvents&&(B.pointerEvents="none");B.position="absolute";var D=this.minWidth,C="";isNaN(D)||(C="min-width:"+(D-2*r)+"px; ");n.innerHTML='
'+this.text+"
";c.chartDiv.appendChild(n);this.textDiv=n;var J=n.offsetWidth,H=n.offsetHeight;n.clientHeight&&(J=n.clientWidth,H=n.clientHeight);A=H+2*v;C=J+2*r;!isNaN(D)&&CA&&(y=A/2),z=b-A/2,a=m&&(z=m-A); +zl&&(D=l-C);var k=z+v,m=D+r,O=this.shadowAlpha,Q=this.shadowColor,r=this.borderThickness,ga=this.bulletSize,I,v=this.fillAlpha,aa=this.borderAlpha;this.showBullet&&(I=d.circle(h,ga,t,v),f.push(I));this.drop?(g=C/1.6,l=0,"V"==x&&(x="down"),"H"==x&&(x="left"),"down"==x&&(D=a+1,z=b-g-g/3),"up"==x&&(l=180,D=a+1,z=b+g+g/3),"left"==x&&(l=270,D=a+g+g/3+2,z=b),"right"==x&&(l=90,D=a-g-g/3+2,z=b),k=z-H/2+1,m=D-J/2-1,q=d.drop(h,g,l,q,v,r,p,aa)):0C-y&&(g=C-y),gA-y&&(x=A-y),xa?C:a-D,C,C,0,0,C]),0this.r-e.width&&(a=this.r-e.width);dthis.processCount&&(this.processCount=1);var b=a.length/this.processCount;this.parseCount=Math.ceil(b)-1;for(var c=0;ca.length&&(c=a.length);var h=this.graphs,f={},g=this.seriesIdField;g||(g=this.categoryField);var k=!1,l,m=this.categoryAxis,n,q,p;m&&(k=m.parseDates,n=m.forceShowField,p=m.classNameField,q=m.labelColorField,l=m.categoryFunction);var t,r,v={},y;k&&(t=d.extractPeriod(m.minPeriod), +r=t.period,t=t.count,y=d.getPeriodDuration(r,t));var x={};this.lookupTable=x;var u,A=this.dataDateFormat,z={};for(u=b;u=y*Q&&(z[O].gap=!0);this.processFields(b,I,aa);I.category=B.category;I.serialDataItem=B;I.graph=b;B.axes[H].graphs[O]= +I;v[O]=B.time;z[O]=I}}}this.chartData[u]=B}if(this.parseCount==e){for(a=0;ab?this.colors[b]:a.lineColorR?a.lineColorR:d.randomColor();a.lineColorR=c}a.fillColorsR=a.fillColors?a.fillColors:a.lineColorR;a.bulletBorderColorR=a.bulletBorderColor?a.bulletBorderColor:a.useLineColorForBulletBorder?a.lineColorR:a.bulletColor;a.bulletColorR=a.bulletColor?a.bulletColor:a.lineColorR;if(c=this.patterns)a.pattern=c[b]},handleLegendEvent:function(a){var b=a.type;if(a=a.dataItem){var c=a.hidden,e=a.showBalloon;switch(b){case "clickMarker":this.textClickEnabled&& +(e?this.hideGraphsBalloon(a):this.showGraphsBalloon(a));break;case "clickLabel":e?this.hideGraphsBalloon(a):this.showGraphsBalloon(a);break;case "rollOverItem":c||this.highlightGraph(a);break;case "rollOutItem":c||this.unhighlightGraph();break;case "hideItem":this.hideGraph(a);break;case "showItem":this.showGraph(a)}}},highlightGraph:function(a){var b=this.graphs;if(b){var c,e=.2;this.legend&&(e=this.legend.rollOverGraphAlpha);if(1!=e)for(c=0;c=b&&(b=.001);if(void 0==h||0===h)h=.01;void 0===f&&(f="#000000");void 0===g&&(g=0);e={fill:c,stroke:f,"fill-opacity":e,"stroke-width":h,"stroke-opacity":g};a=isNaN(l)?a.circle(0,0,b).attr(e):a.ellipse(0,0,b,l).attr(e);k&&a.gradient("radialGradient",[c,d.adjustLuminosity(c,-.6)]);return a};d.text=function(a,b,c,e,h,f,g,k){f||(f="middle");"right"==f&&(f="end");"left"==f&&(f="start");isNaN(k)&&(k=1);void 0!==b&&(b=String(b),d.isIE&& +!d.isModern&&(b=b.replace("&","&"),b=b.replace("&","&")));c={fill:c,"font-family":e,"font-size":h+"px",opacity:k};!0===g&&(c["font-weight"]="bold");c["text-anchor"]=f;return a.text(b,c)};d.polygon=function(a,b,c,e,h,f,g,k,l,m,n){isNaN(f)&&(f=.01);isNaN(k)&&(k=h);var q=e,p=!1;"object"==typeof q&&1b&&(b=Math.abs(b),t=-b);0>c&&(c=Math.abs(c),r=-c);t+=d.dx;r+=d.dy;h={fill:q,stroke:g,"fill-opacity":h,"stroke-opacity":k};void 0!==n&&0=x&&(h=x);var u=1/180*Math.PI,x=b+Math.sin(e*u)*k,A=c-Math.cos(e*u)*v,z=b+Math.sin(e*u)*f,B=c-Math.cos(e*u)*g,D=b+Math.sin((e+h)*u)*f,C=c-Math.cos((e+h)*u)*g,J=b+Math.sin((e+h)*u)*k,u=c-Math.cos((e+h)*u)*v,H={fill:d.adjustLuminosity(m.fill,-.2),"stroke-opacity":0,"fill-opacity":m["fill-opacity"]},S=0;180Math.abs(h)&&1>=Math.abs(D-z)&&1>=Math.abs(C-B)&&(O=!0));h="";var Q;q&&(H["fill-opacity"]=0,H["stroke-opacity"]=m["stroke-opacity"]/2,H.stroke=m.stroke);if(0a.length&&(a=String(a[0])+String(a[0])+String(a[1])+String(a[1])+String(a[2])+String(a[2]));b=b||0;var c="#",e,h;for(h=0;3>h;h++)e=parseInt(a.substr(2*h,2),16),e=Math.round(Math.min(Math.max(0,e+e*b),255)).toString(16),c+=("00"+ +e).substr(e.length);return c}})();(function(){var d=window.AmCharts;d.Bezier=d.Class({construct:function(a,b,c,e,h,f,g,k,l,m,n){var q,p;"object"==typeof g&&1c&&(k=c);b.push({x:l.x-k/h,y:l.y-e/f});b.push({x:l.x,y:l.y});b.push({x:l.x+ +k/h,y:l.y+e/f})}e=a[a.length-1].y-a[a.length-2].y;c=a[a.length-1].x-a[a.length-2].x;b.push({x:a[a.length-1].x-c/h,y:a[a.length-1].y-e/f});b.push({x:a[a.length-1].x,y:a[a.length-1].y});return b},drawBeziers:function(a){var b="",c;for(c=0;c<(a.length-1)/3;c++)b+=this.drawBezierMidpoint(a[3*c],a[3*c+1],a[3*c+2],a[3*c+3]);return b},drawBezierMidpoint:function(a,b,c,d){var h=Math.round,f=this.getPointOnSegment(a,b,.75),g=this.getPointOnSegment(d,c,.75),k=(d.x-a.x)/16,l=(d.y-a.y)/16,m=this.getPointOnSegment(a, +b,.375);a=this.getPointOnSegment(f,g,.375);a.x-=k;a.y-=l;b=this.getPointOnSegment(g,f,.375);b.x+=k;b.y+=l;c=this.getPointOnSegment(d,c,.375);k=this.getMiddle(m,a);f=this.getMiddle(f,g);g=this.getMiddle(b,c);m=" Q"+h(m.x)+","+h(m.y)+","+h(k.x)+","+h(k.y);m+=" Q"+h(a.x)+","+h(a.y)+","+h(f.x)+","+h(f.y);m+=" Q"+h(b.x)+","+h(b.y)+","+h(g.x)+","+h(g.y);return m+=" Q"+h(c.x)+","+h(c.y)+","+h(d.x)+","+h(d.y)},getMiddle:function(a,b){return{x:(a.x+b.x)/2,y:(a.y+b.y)/2}},getPointOnSegment:function(a,b,c){return{x:a.x+ +(b.x-a.x)*c,y:a.y+(b.y-a.y)*c}}})})();(function(){var d=window.AmCharts;d.AmDraw=d.Class({construct:function(a,b,c,e){d.SVG_NS="http://www.w3.org/2000/svg";d.SVG_XLINK="http://www.w3.org/1999/xlink";d.hasSVG=!!document.createElementNS&&!!document.createElementNS(d.SVG_NS,"svg").createSVGRect;1>b&&(b=10);1>c&&(c=10);this.div=a;this.width=b;this.height=c;this.rBin=document.createElement("div");d.hasSVG?(d.SVG=!0,b=this.createSvgElement("svg"),a.appendChild(b),this.container=b,this.addDefs(e),this.R=new d.SVGRenderer(this)):d.isIE&&d.VMLRenderer&& +(d.VML=!0,d.vmlStyleSheet||(document.namespaces.add("amvml","urn:schemas-microsoft-com:vml"),31>document.styleSheets.length?(b=document.createStyleSheet(),b.addRule(".amvml","behavior:url(#default#VML); display:inline-block; antialias:true"),d.vmlStyleSheet=b):document.styleSheets[0].addRule(".amvml","behavior:url(#default#VML); display:inline-block; antialias:true")),this.container=a,this.R=new d.VMLRenderer(this,e),this.R.disableSelection(a))},createSvgElement:function(a){return document.createElementNS(d.SVG_NS, +a)},circle:function(a,b,c,e){var h=new d.AmDObject("circle",this);h.attr({r:c,cx:a,cy:b});this.addToContainer(h.node,e);return h},ellipse:function(a,b,c,e,h){var f=new d.AmDObject("ellipse",this);f.attr({rx:c,ry:e,cx:a,cy:b});this.addToContainer(f.node,h);return f},setSize:function(a,b){0c&&(c=1);1>e&&(e=1);k.attr({x:a,y:b,width:c,height:e,rx:h,ry:h,"stroke-width":f});this.addToContainer(k.node,g);return k},image:function(a,b,c,e,h,f){var g=new d.AmDObject("image",this);g.attr({x:b,y:c,width:e,height:h});this.R.path(g,a);this.addToContainer(g.node,f);return g},addToContainer:function(a,b){b||(b=this.container);b.appendChild(a)},text:function(a,b,c){return this.R.text(a,b,c)},path:function(a,b,c,e){var h=new d.AmDObject("path",this);e||(e="100,100"); +h.attr({cs:e});c?h.attr({dd:a}):h.attr({d:a});this.addToContainer(h.node,b);return h},set:function(a){return this.R.set(a)},remove:function(a){if(a){var b=this.rBin;b.appendChild(a);b.innerHTML=""}},renderFix:function(){var a=this.container,b=a.style;b.top="0px";b.left="0px";try{var c=a.getBoundingClientRect(),d=c.left-Math.round(c.left),h=c.top-Math.round(c.top);d&&(b.left=d+"px");h&&(b.top=h+"px")}catch(f){}},update:function(){this.R.update()},addDefs:function(a){if(d.hasSVG){var b=this.createSvgElement("desc"), +c=this.container;c.setAttribute("version","1.1");c.style.position="absolute";this.setSize(this.width,this.height);if(a.accessibleTitle){var e=this.createSvgElement("text");c.appendChild(e);e.innerHTML=a.accessibleTitle;e.style.opacity=0}d.rtl&&(c.setAttribute("direction","rtl"),c.style.left="auto",c.style.right="0px");a&&(a.addCodeCredits&&b.appendChild(document.createTextNode("JavaScript chart by amCharts "+a.version)),c.appendChild(b),a.defs&&(b=this.createSvgElement("defs"),c.appendChild(b),d.parseDefs(a.defs, +b),this.defs=b))}}})})();(function(){var d=window.AmCharts;d.AmDObject=d.Class({construct:function(a,b){this.D=b;this.R=b.R;this.node=this.R.create(this,a);this.y=this.x=0;this.scale=1},attr:function(a){this.R.attr(this,a);return this},getAttr:function(a){return this.node.getAttribute(a)},setAttr:function(a,b){this.R.setAttr(this,a,b);return this},clipRect:function(a,b,c,d){this.R.clipRect(this,a,b,c,d)},translate:function(a,b,c,d){d||(a=Math.round(a),b=Math.round(b));this.R.move(this,a,b,c);this.x=a;this.y=b;this.scale= +c;this.angle&&this.rotate(this.angle)},rotate:function(a,b){this.R.rotate(this,a,b);this.angle=a},animate:function(a,b,c){for(var e in a)if(a.hasOwnProperty(e)){var h=e,f=a[e];c=d.getEffect(c);this.R.animate(this,h,f,b,c)}},push:function(a){if(a){var b=this.node;b.appendChild(a.node);var c=a.clipPath;c&&b.appendChild(c);(a=a.grad)&&b.appendChild(a)}},text:function(a){this.R.setText(this,a)},remove:function(){this.stop();this.R.remove(this)},clear:function(){var a=this.node;if(a.hasChildNodes())for(;1<= +a.childNodes.length;)a.removeChild(a.firstChild)},hide:function(){this.setAttr("visibility","hidden")},show:function(){this.setAttr("visibility","visible")},getBBox:function(){return this.R.getBBox(this)},toFront:function(){var a=this.node;if(a){this.prevNextNode=a.nextSibling;var b=a.parentNode;b&&b.appendChild(a)}},toPrevious:function(){var a=this.node;a&&this.prevNextNode&&(a=a.parentNode)&&a.insertBefore(this.prevNextNode,null)},toBack:function(){var a=this.node;if(a){this.prevNextNode=a.nextSibling; +var b=a.parentNode;if(b){var c=b.firstChild;c&&b.insertBefore(a,c)}}},mouseover:function(a){this.R.addListener(this,"mouseover",a);return this},mouseout:function(a){this.R.addListener(this,"mouseout",a);return this},click:function(a){this.R.addListener(this,"click",a);return this},dblclick:function(a){this.R.addListener(this,"dblclick",a);return this},mousedown:function(a){this.R.addListener(this,"mousedown",a);return this},mouseup:function(a){this.R.addListener(this,"mouseup",a);return this},touchmove:function(a){this.R.addListener(this, +"touchmove",a);return this},touchstart:function(a){this.R.addListener(this,"touchstart",a);return this},touchend:function(a){this.R.addListener(this,"touchend",a);return this},keyup:function(a){this.R.addListener(this,"keyup",a);return this},focus:function(a){this.R.addListener(this,"focus",a);return this},blur:function(a){this.R.addListener(this,"blur",a);return this},contextmenu:function(a){this.node.addEventListener?this.node.addEventListener("contextmenu",a,!0):this.R.addListener(this,"contextmenu", +a);return this},stop:function(){d.removeFromArray(this.R.animations,this.an_translate);d.removeFromArray(this.R.animations,this.an_y);d.removeFromArray(this.R.animations,this.an_x)},length:function(){return this.node.childNodes.length},gradient:function(a,b,c){this.R.gradient(this,a,b,c)},pattern:function(a,b,c){a&&this.R.pattern(this,a,b,c)}})})();(function(){var d=window.AmCharts;d.VMLRenderer=d.Class({construct:function(a,b){this.chart=b;this.D=a;this.cNames={circle:"oval",ellipse:"oval",rect:"roundrect",path:"shape"};this.styleMap={x:"left",y:"top",width:"width",height:"height","font-family":"fontFamily","font-size":"fontSize",visibility:"visibility"}},create:function(a,b){var c;if("group"==b)c=document.createElement("div"),a.type="div";else if("text"==b)c=document.createElement("div"),a.type="text";else if("image"==b)c=document.createElement("img"), +a.type="image";else{a.type="shape";a.shapeType=this.cNames[b];c=document.createElement("amvml:"+this.cNames[b]);var d=document.createElement("amvml:stroke");c.appendChild(d);a.stroke=d;var h=document.createElement("amvml:fill");c.appendChild(h);a.fill=h;h.className="amvml";d.className="amvml";c.className="amvml"}c.style.position="absolute";c.style.top=0;c.style.left=0;return c},path:function(a,b){a.node.setAttribute("src",b)},setAttr:function(a,b,c){if(void 0!==c){var e;8===document.documentMode&& +(e=!0);var h=a.node,f=a.type,g=h.style;"r"==b&&(g.width=2*c,g.height=2*c);"oval"==a.shapeType&&("rx"==b&&(g.width=2*c),"ry"==b&&(g.height=2*c));"roundrect"==a.shapeType&&("width"!=b&&"height"!=b||--c);"cursor"==b&&(g.cursor=c);"cx"==b&&(g.left=c-d.removePx(g.width)/2);"cy"==b&&(g.top=c-d.removePx(g.height)/2);var k=this.styleMap[b];"width"==k&&0>c&&(c=0);void 0!==k&&(g[k]=c);"text"==f&&("text-anchor"==b&&(a.anchor=c,k=h.clientWidth,"end"==c&&(g.marginLeft=-k+"px"),"middle"==c&&(g.marginLeft=-(k/2)+ +"px",g.textAlign="center"),"start"==c&&(g.marginLeft="0px")),"fill"==b&&(g.color=c),"font-weight"==b&&(g.fontWeight=c));if(g=a.children)for(k=0;kc&&(g="dot"),3<=c&&6>=c&&(g="dash"),6g&&(b+=g);0>k&&(c+=k)}return{x:b,y:c,width:d,height:h}},setText:function(a,b){var c=a.node;c&&(c.innerHTML=b);this.setAttr(a,"text-anchor",a.anchor)},addListener:function(a,b,c){a.node["on"+b]=c},move:function(a,b,c){var e=a.node,h=e.style;"text"==a.type&&(c-=d.removePx(h.fontSize)/2-1);"oval"==a.shapeType&&(b-=d.removePx(h.width)/2,c-=d.removePx(h.height)/2);a=a.bw;isNaN(a)||(b-=a,c-=a);isNaN(b)||isNaN(c)||(e.style.left=b+"px",e.style.top= +c+"px")},svgPathToVml:function(a){var b=a.split(" ");a="";var c,d=Math.round,h;for(h=0;hthis.fontSize&&(this.ly=h/2-1);0p&&(p=z);u=u.height;u>t&&(t=u)}var z=t=0,B=f,D=0,C=0;for(A=0;AC&&(C=u.height);H+u.width>q&&0=l&&(z=0,t++,D=D+C+m,B=f,C=0);y.push(J)}u=y.getBBox();l=u.height+2*m-1;"left"==a||"right"==a?(n=u.width+2*f,k=n+b+c,g.style.width=k+"px",this.ieW=k):n=k-b-c-1;c=d.polygon(this.container,[0,n,n,0],[0,0,l,l],this.backgroundColor,this.backgroundAlpha,1,this.borderColor,this.borderAlpha);d.setCN(this.chart, +c,"legend-bg");v.push(c);v.translate(b,e);c.toBack();b=f;if("top"==a||"bottom"==a||"absolute"==a||"outside"==a)"center"==this.align?b=f+(n-u.width)/2:"right"==this.align&&(b=f+n-u.width);y.translate(b,m+1);this.titleHeight>l&&(l=this.titleHeight);a=l+e+h+1;0>a&&(a=0);a>this.chart.divRealHeight&&(g.style.top="0px");g.style.height=Math.round(a)+"px";r.setSize(this.divWidth,a)},createEntry:function(a){if(!1!==a.visibleInLegend&&!a.hideFromLegend){var b=this,c=b.chart,e=b.useGraphSettings,h=a.markerType; +h&&(e=!1);a.legendEntryWidth=b.markerSize;h||(h=b.markerType);var f=a.color,g=a.alpha;a.legendKeyColor&&(f=a.legendKeyColor());a.legendKeyAlpha&&(g=a.legendKeyAlpha());var k;!0===a.hidden&&(k=f=b.markerDisabledColor);var l=a.pattern,m=a.customMarker;m||(m=b.customMarker);var n=b.container,q=b.markerSize,p=0,t=0,r=q/2;if(e){e=a.type;b.switchType=void 0;if("line"==e||"step"==e||"smoothedLine"==e||"ohlc"==e)l=n.set(),a.hidden||(f=a.lineColorR,k=a.bulletBorderColorR),p=d.line(n,[0,2*q],[q/2,q/2],f,a.lineAlpha, +a.lineThickness,a.dashLength),d.setCN(c,p,"graph-stroke"),l.push(p),a.bullet&&(a.hidden||(f=a.bulletColorR),p=d.bullet(n,a.bullet,a.bulletSize,f,a.bulletAlpha,a.bulletBorderThickness,k,a.bulletBorderAlpha))&&(d.setCN(c,p,"graph-bullet"),p.translate(q+1,q/2),l.push(p)),r=0,p=q,t=q/3;else{var v;a.getGradRotation&&(v=a.getGradRotation(),0===v&&(v=180));p=a.fillColorsR;!0===a.hidden&&(p=f);if(l=b.createMarker("rectangle",p,a.fillAlphas,a.lineThickness,f,a.lineAlpha,v,l,a.dashLength))r=q,l.translate(r, +q/2);p=q}d.setCN(c,l,"graph-"+e);d.setCN(c,l,"graph-"+a.id)}else if(m)l=n.image(m,0,0,q,q);else{var y;isNaN(b.gradientRotation)||(y=180+b.gradientRotation);(l=b.createMarker(h,f,g,void 0,void 0,void 0,y,l))&&l.translate(q/2,q/2)}d.setCN(c,l,"legend-marker");b.addListeners(l,a);n=n.set([l]);b.switchable&&a.switchable&&n.setAttr("cursor","pointer");void 0!==a.id&&d.setCN(c,n,"legend-item-"+a.id);d.setCN(c,n,a.className,!0);k=b.switchType;var x;k&&"none"!=k&&0c&&(d="00"+c);10<=c&&100>c&&(d="0"+c);a=a.replace(/fff/g,d)}return a};d.extractPeriod=function(a){var b=d.stripNumbers(a),c=1;b!=a&&(c=Number(a.slice(0,a.indexOf(b))));return{period:b,count:c}};d.getDate=function(a,b,c){return a instanceof Date?d.newDate(a,c):b&&isNaN(a)?d.stringToDate(a,b):new Date(a)};d.daysInMonth=function(a){return(new Date(a.getYear(),a.getMonth()+ +1,0)).getDate()};d.newDate=function(a,b){return b&&-1==b.indexOf("fff")?new Date(a):new Date(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours(),a.getMinutes(),a.getSeconds(),a.getMilliseconds())};d.resetDateToMin=function(a,b,c,e){void 0===e&&(e=1);var h,f,g,k,l,m,n;d.useUTC?(h=a.getUTCFullYear(),f=a.getUTCMonth(),g=a.getUTCDate(),k=a.getUTCHours(),l=a.getUTCMinutes(),m=a.getUTCSeconds(),n=a.getUTCMilliseconds(),a=a.getUTCDay()):(h=a.getFullYear(),f=a.getMonth(),g=a.getDate(),k=a.getHours(),l= +a.getMinutes(),m=a.getSeconds(),n=a.getMilliseconds(),a=a.getDay());switch(b){case "YYYY":h=Math.floor(h/c)*c;f=0;g=1;n=m=l=k=0;break;case "MM":f=Math.floor(f/c)*c;g=1;n=m=l=k=0;break;case "WW":g=a>=e?g-a+e:g-(7+a)+e;n=m=l=k=0;break;case "DD":n=m=l=k=0;break;case "hh":k=Math.floor(k/c)*c;n=m=l=0;break;case "mm":l=Math.floor(l/c)*c;n=m=0;break;case "ss":m=Math.floor(m/c)*c;n=0;break;case "fff":n=Math.floor(n/c)*c}d.useUTC?(a=new Date,a.setUTCFullYear(h,f,g),a.setUTCHours(k,l,m,n)):a=new Date(h,f,g, +k,l,m,n);return a};d.getPeriodDuration=function(a,b){void 0===b&&(b=1);var c;switch(a){case "YYYY":c=316224E5;break;case "MM":c=26784E5;break;case "WW":c=6048E5;break;case "DD":c=864E5;break;case "hh":c=36E5;break;case "mm":c=6E4;break;case "ss":c=1E3;break;case "fff":c=1}return c*b};d.intervals={s:{nextInterval:"ss",contains:1E3},ss:{nextInterval:"mm",contains:60,count:0},mm:{nextInterval:"hh",contains:60,count:1},hh:{nextInterval:"DD",contains:24,count:2},DD:{nextInterval:"",contains:Infinity,count:3}}; +d.getMaxInterval=function(a,b){var c=d.intervals;return a>=c[b].contains?(a=Math.round(a/c[b].contains),b=c[b].nextInterval,d.getMaxInterval(a,b)):"ss"==b?c[b].nextInterval:b};d.dayNames="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");d.shortDayNames="Sun Mon Tue Wed Thu Fri Sat".split(" ");d.monthNames="January February March April May June July August September October November December".split(" ");d.shortMonthNames="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "); +d.getWeekNumber=function(a){a=new Date(a);a.setHours(0,0,0);a.setDate(a.getDate()+4-(a.getDay()||7));var b=new Date(a.getFullYear(),0,1);return Math.ceil(((a-b)/864E5+1)/7)};d.stringToDate=function(a,b){var c={},e=[{pattern:"YYYY",period:"year"},{pattern:"YY",period:"year"},{pattern:"MM",period:"month"},{pattern:"M",period:"month"},{pattern:"DD",period:"date"},{pattern:"D",period:"date"},{pattern:"JJ",period:"hours"},{pattern:"J",period:"hours"},{pattern:"HH",period:"hours"},{pattern:"H",period:"hours"}, +{pattern:"KK",period:"hours"},{pattern:"K",period:"hours"},{pattern:"LL",period:"hours"},{pattern:"L",period:"hours"},{pattern:"NN",period:"minutes"},{pattern:"N",period:"minutes"},{pattern:"SS",period:"seconds"},{pattern:"S",period:"seconds"},{pattern:"QQQ",period:"milliseconds"},{pattern:"QQ",period:"milliseconds"},{pattern:"Q",period:"milliseconds"}],h=!0,f=b.indexOf("AA");-1!=f&&(a.substr(f,2),"pm"==a.toLowerCase&&(h=!1));var f=b,g,k,l;for(l=0;lr&&(r="0"+r);b=b.replace(/JJ/g,r);b=b.replace(/J/g,q);r=k;0===r&&(r=24,-1!=b.indexOf("H")&&(f--,0===f&&(e=new Date(a),e.setDate(e.getDate()-1),h=e.getMonth(),f=e.getDate(),e=e.getFullYear())));a=h+1;9>h&&(a="0"+a);q=f;10>f&&(q="0"+f);var v=r;10>v&&(v="0"+v);b=b.replace(/HH/g,v);b=b.replace(/H/g,r);r=k;11v&&(v="0"+v);b=b.replace(/KK/g,v);b=b.replace(/K/g,r);r=k;0===r&&(r=12);12v&&(v="0"+v);b=b.replace(/LL/g,v);b=b.replace(/L/g,r); +r=l;10>r&&(r="0"+r);b=b.replace(/NN/g,r);b=b.replace(/N/g,l);l=m;10>l&&(l="0"+l);b=b.replace(/SS/g,l);b=b.replace(/S/g,m);m=n;10>m?m="00"+m:100>m&&(m="0"+m);l=n;10>l&&(l="00"+l);b=b.replace(/A/g,"@A@");b=b.replace(/QQQ/g,m);b=b.replace(/QQ/g,l);b=b.replace(/Q/g,n);b=b.replace(/YYYY/g,"@IIII@");b=b.replace(/YY/g,"@II@");b=b.replace(/MMMM/g,"@XXXX@");b=b.replace(/MMM/g,"@XXX@");b=b.replace(/MM/g,"@XX@");b=b.replace(/M/g,"@X@");b=b.replace(/DD/g,"@RR@");b=b.replace(/D/g,"@R@");b=b.replace(/EEEE/g,"@PPPP@"); +b=b.replace(/EEE/g,"@PPP@");b=b.replace(/EE/g,"@PP@");b=b.replace(/E/g,"@P@");b=b.replace(/@IIII@/g,e);b=b.replace(/@II@/g,p);b=b.replace(/@XXXX@/g,c.monthNames[h]);b=b.replace(/@XXX@/g,c.shortMonthNames[h]);b=b.replace(/@XX@/g,a);b=b.replace(/@X@/g,h+1);b=b.replace(/@RR@/g,q);b=b.replace(/@R@/g,f);b=b.replace(/@PPPP@/g,c.dayNames[g]);b=b.replace(/@PPP@/g,c.shortDayNames[g]);b=b.replace(/@PP@/g,t);b=b.replace(/@P@/g,g);return b=12>k?b.replace(/@A@/g,c.amString):b.replace(/@A@/g,c.pmString)};d.changeDate= +function(a,b,c,e,h){if(d.useUTC)return d.changeUTCDate(a,b,c,e,h);var f=-1;void 0===e&&(e=!0);void 0===h&&(h=!1);!0===e&&(f=1);switch(b){case "YYYY":a.setFullYear(a.getFullYear()+c*f);e||h||a.setDate(a.getDate()+1);break;case "MM":b=a.getMonth();a.setMonth(a.getMonth()+c*f);a.getMonth()>b+c*f&&a.setDate(a.getDate()-1);e||h||a.setDate(a.getDate()+1);break;case "DD":a.setDate(a.getDate()+c*f);break;case "WW":a.setDate(a.getDate()+c*f*7);break;case "hh":a.setHours(a.getHours()+c*f);break;case "mm":a.setMinutes(a.getMinutes()+ +c*f);break;case "ss":a.setSeconds(a.getSeconds()+c*f);break;case "fff":a.setMilliseconds(a.getMilliseconds()+c*f)}return a};d.changeUTCDate=function(a,b,c,d,h){var f=-1;void 0===d&&(d=!0);void 0===h&&(h=!1);!0===d&&(f=1);switch(b){case "YYYY":a.setUTCFullYear(a.getUTCFullYear()+c*f);d||h||a.setUTCDate(a.getUTCDate()+1);break;case "MM":b=a.getUTCMonth();a.setUTCMonth(a.getUTCMonth()+c*f);a.getUTCMonth()>b+c*f&&a.setUTCDate(a.getUTCDate()-1);d||h||a.setUTCDate(a.getUTCDate()+1);break;case "DD":a.setUTCDate(a.getUTCDate()+ +c*f);break;case "WW":a.setUTCDate(a.getUTCDate()+c*f*7);break;case "hh":a.setUTCHours(a.getUTCHours()+c*f);break;case "mm":a.setUTCMinutes(a.getUTCMinutes()+c*f);break;case "ss":a.setUTCSeconds(a.getUTCSeconds()+c*f);break;case "fff":a.setUTCMilliseconds(a.getUTCMilliseconds()+c*f)}return a}})(); diff --git a/app/lib/amstock.js b/app/lib/amstock.js new file mode 100644 index 0000000..ec013b3 --- /dev/null +++ b/app/lib/amstock.js @@ -0,0 +1,104 @@ +(function(){var d=window.AmCharts;d.AmStockChart=d.Class({construct:function(a){this.type="stock";this.cname="AmStockChart";d.addChart(this);this.version="3.20.12";this.theme=a;this.createEvents("buildStarted","zoomed","rollOverStockEvent","rollOutStockEvent","clickStockEvent","panelRemoved","dataUpdated","init","rendered","drawn","resized");this.colors="#FF6600 #FCD202 #B0DE09 #0D8ECF #2A0CD0 #CD0D74 #CC0000 #00CC00 #0000CC #DDDDDD #999999 #333333 #990000".split(" ");this.firstDayOfWeek=1;this.glueToTheEnd= +!1;this.dataSetCounter=-1;this.zoomOutOnDataSetChange=!1;this.panels=[];this.dataSets=[];this.chartCursors=[];this.comparedDataSets=[];this.classNamePrefix="amcharts";this.categoryAxesSettings=new d.CategoryAxesSettings(a);this.valueAxesSettings=new d.ValueAxesSettings(a);this.panelsSettings=new d.PanelsSettings(a);this.chartScrollbarSettings=new d.ChartScrollbarSettings(a);this.chartCursorSettings=new d.ChartCursorSettings(a);this.stockEventsSettings=new d.StockEventsSettings(a);this.legendSettings= +new d.LegendSettings(a);this.balloon=new d.AmBalloon(a);this.previousEndDate=new Date(0);this.previousStartDate=new Date(0);this.dataSetCount=this.graphCount=0;this.chartCreated=!1;this.processTimeout=0;this.autoResize=this.extendToFullPeriod=!0;d.applyTheme(this,a,this.cname)},write:function(a){var b=this;if(b.listeners)for(var c in b.listeners){var e=b.listeners[c];b.addListener(e.event,e.method)}b.fire({type:"buildStarted",chart:b});b.afterWriteTO&&clearTimeout(b.afterWriteTO);0c?this.colors[c]:d.randomColor())}!a&&d.ifArray(b)&&(this.mainDataSet=this.dataSets[0]);this.getSelections()},getLastDate:function(a){var b=d.getDate(a,this.dataDateFormat,"fff");a=this.categoryAxesSettings.minPeriod; +b=d.changeDate(b,this.categoryAxesSettings.minPeriod,1,!0).getTime();-1==a.indexOf("fff")&&--b;return new Date(b)},getFirstDate:function(a){a=d.getDate(a,this.dataDateFormat,"fff");return new Date(d.resetDateToMin(a,this.categoryAxesSettings.minPeriod,1,this.firstDayOfWeek))},updateData:function(){var a=this,b=a.mainDataSet;if(b){a.parsingData=!1;var c=a.categoryAxesSettings;-1==d.getItemIndex(c.minPeriod,c.groupToPeriods)&&c.groupToPeriods.unshift(c.minPeriod);var e=b.dataProvider;if(d.ifArray(e)){var h= +b.categoryField;a.firstDate=a.getFirstDate(e[0][h]);a.lastDate=a.getLastDate(e[e.length-1][h]);a.periodSelector&&a.periodSelector.setRanges(a.firstDate,a.lastDate);b.dataParsed||(a.parsingData=!0,0=y[t]||!y[t]){r[t]={};r[t].amCategoryIdField=String(d.resetDateToMin(E,F,H,e).getTime());var G;for(G=0;Gf&&(u=d.newDate(E,b),u=d.changeDate(u, +F,H,!0),u=d.resetDateToMin(u,F,H,e),y[t]=u.getTime());if(C==f)for(var I in B)B.hasOwnProperty(I)&&(r[t][I]=B[I]);r[t][p]=d.newDate(E,b)}else for(F=0;Fv[u+"High"]&&(v[u+"High"]=x),isNaN(v[u+"AbsHigh"])&&(v[u+"AbsHigh"]=x),Math.abs(x)>v[u+"AbsHigh"]&&(v[u+"AbsHigh"]=x),v[u+"Close"]=x,H=d.getDecimals(v[u+ +"Sum"]),G=d.getDecimals(x),isNaN(v[u+"Sum"])&&(v[u+"Sum"]=0),v[u+"Sum"]+=x,v[u+"Sum"]=d.roundTo(v[u+"Sum"],Math.max(H,G)),v[u+"Count"]++,v[u+"Average"]=v[u+"Sum"]/v[u+"Count"]))}}}a.agregatedDataProviders=m;d.ifArray(a.stockEvents)?0=l&&a=h-e))return ak.getTime()&&(a=k);b.getTime()k.getTime()&&(b=k);r=d.getItemIndex(p,g.groupToPeriods);p=m;m=c.choosePeriod(r,a,b);c.currentPeriod=m;var r=d.extractPeriod(m),z=d.getPeriodDuration(r.period,r.count);1>b.getTime()-a.getTime()&&(a=new Date(b.getTime()-1));var A=d.newDate(a);c.extendToFullPeriod&&(A.getTime()-h.getTime()<.1*z&&(A=d.resetDateToMin(a,r.period,r.count,y)),k.getTime()-b.getTime()<.1*z&&(b=d.resetDateToMin(k,r.period,r.count,y),b=d.changeDate(b,r.period,r.count,!0)));for(h=0;hn&&0n.getTime()&&(l=d.getPeriodDuration("DD",1),g=new Date(n.getTime()-l)),g.getTime()h&&(e=h);h=this.theme;this.unselectButtons();var k;for(k=b.length-1;0<=k;k--){var m=b[k],g=m.button;m.startTime&&m.endTime&&c==m.startTime&&e==m.endTime&&(this.unselectButtons(),g.className="amChartsButtonSelected "+a.classNamePrefix+"-period-input-selected", +h&&d.applyStyles(g.style,h.PeriodButtonSelected))}}this.skipMark=!1},unselectButtons:function(){var a=this.chart,b=this.periods,c,e=this.theme;for(c=b.length-1;0<=c;c--){var h=b[c].button;h.className="amChartsButton "+a.classNamePrefix+"-period-input";e&&d.applyStyles(h.style,e.PeriodButton)}},setDefaultPeriod:function(){var a=this.periods,b;if(this.chart.chartCreated)for(b=0;ba?(e=this.startTime+e*d,d=this.endTime+1*d):(e=this.startTime-e*d,d=this.endTime-1*d);ethis.lastTime&&(d=this.lastTime);ethis.availableSpace&&(this.stackDown=!0);this.set=a.set();this.cset=a.set();this.set.push(this.cset);this.set.doNotScale=!0;a=0;var c;for(c=0;c + * @copyright Dominique Müller 2015 + * @license MIT + * @link Github + * @version 1.0.0 + */ + +/** + * Universal module definition (UMD), dependency-free + */ +( function( root, factory ) { + + 'use strict'; + + if ( typeof define === 'function' && define.amd ) { + + // AMD module, anonymous + define( factory ); + + } else if ( typeof module === 'object' && module.exports ) { + + // Node module, CommonJS-like + module.exports = factory(); + + } else { + + // Browser global (root is window) + root.notification = factory(); + + } + +}( this, function() { + + 'use strict'; + + /* ========== POLYFILL FOR CUSTOM EVENTS ========== */ + + /** + * Polyfill for creating custom events in IE9+ + * + * Taken from the Mozilla developer site: + * + */ + ( function( window ) { + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + CustomEvent.prototype = window.Event.prototype; + window.CustomEvent = CustomEvent; + } )( window ); + + /* ========== DEFAULT SYMBOLS ========== */ + + /** + * Default svg symbols + * @type {Object} + */ + var icons = { + + /** + * Success symbol + * @type {String} + */ + success: '' + + '' + + '', + + /** + * Error symbol + * @type {String} + */ + error: '' + + '' + + '' + + '' + + }; + + /* ========== PROFILES ========== */ + + /** + * Profiles object (static) + * @type {Object} + */ + var Profiles = ( function() { + + /** + * List of profiles + * @type {Object} + */ + var list = { + + /** + * Global profile, contains all available options with their default (and also recommended) values + * @type {Object} + */ + global: { + + notification: { + + /** + * Defines the position on the screen (x, y) + * @type {Array} + * @default [ 'left', 'bottom' ] + */ + position: [ 'left', 'bottom' ], + + /** + * Defines distances (x to screen in px, y to screen in px, y between in px) + * @type {Array} + * @default [ 20, 20, 10 ] + */ + distances: [ 20, 20, 10 ], + + /** + * Defines the height (in px) + * @type {Number} + * @default 60 + */ + height: 60, + + /** + * Defines the maximum width (in px) + * @type {Boolean | Number} + * @default false + */ + maxWidth: false, + + /** + * Defines the corner roundness (all four corners in px) + * @type {Boolean | Array} + * @default [ 1, 1, 1, 1 ] + */ + roundCorners: [ 1, 1, 1, 1 ], + + /** + * Defines the background color (in HEX / RGB / RGBA) + * @type {String} + * @default '#555' + */ + color: '#555' + + }, + + symbol: { + + /** + * Defines the visibility (enabled / disabled) + * @type {Boolean} + * @default true + */ + visible: false, + + /** + * Defines the resource (default / url / function returning svg) + * @type {Boolean | String | Function} + * @default false + */ + resource: false, + + /** + * Defines the corner roundness (all four corners in px) + * @type {Boolean | Array} + * @default false + */ + roundCorners: false, + + /** + * Defines the highlight color (in HEX / RGB / RGBA) + * @type {Boolean | String} + * @default 'rgba(0,0,0,.1)' + */ + color: 'rgba(0,0,0,.1)' + + }, + + message: { + + /** + * Defines the visibility (enabled / disabled) + * @type {Boolean} + * @default true + */ + visible: true, + + /** + * Defines the font color (in HEX / RGB / RGBA) + * @type {String} + * @default '#FFF' + */ + color: '#FFF', + + /** + * Defines the font size (in px) + * @type {Number} + * @default 14 + */ + textSize: 14 + + }, + + dismiss: { + + /** + * Defines the visibility (enabled / disabled) + * @type {Boolean} + * @default true + */ + visible: true, + + /** + * Defines the font color (in HEX / RGB / RGBA) + * @type {String} + * @default '#FFF' + */ + color: '#FFF', + + /** + * Defines the dismiss text (enabled / text) + * @type {Boolean | String} + * @default false + */ + text: false + + }, + + behaviour: { + + /** + * Defines the auto hide behaviour (disabled / duration in s) + * @type {Boolean | Number} + * @default 5 + */ + autoHide: 5, + + /** + * Defines the mouseover action (disabled / 'pause' / 'reset') + * @type {Boolean | String} + * @default 'pause' + */ + onMouseover: 'pause', + + /** + * Defines the stacking behaviour (disabled / enabled) + * @type {Boolean} + * @default true + */ + stacking: true, + + /** + * Defines the limit of opened notifications (disabled / enabled / positiv number / negative number) + * @type {Boolean | Number} + * @default true + */ + limit: true, + + /** + * Defines the HTML mode for the message (disabled / enabled) + * @type {Boolean} + * @default false + */ + htmlMode: false + + }, + + animations: { + + /** + * Defines the animations behaviour (disabled / enabled) + * @type {Boolean} + * @default true + */ + enabled: true, + + /** + * Defines the animation durations (animate-in in s, animate-out in s) + * @type {Array} + * @default [ 0.75, 0.75 ] + */ + duration: [ 0.75, 0.75 ], + + /** + * Defines the animation easings (animate-in, animate-out) + * @type {Array} + * @default [ 'ease', 'ease' ] + */ + easing: [ 'ease', 'ease' ] + + }, + + callbacks: { + + /** + * Defines the callback function for the onOpen event + * @type {Boolean | Function} + * @default false + */ + onOpen: false, + + /** + * Defines the callback function for the onOpened event + * @type {Boolean | Function} + * @default false + */ + onOpened: false, + + /** + * Defines the callback function for the onClose event + * @type {Boolean | Function} + * @default false + */ + onClose: false, + + /** + * Defines the callback function for the onClosed event + * @type {Boolean | Function} + * @default false + */ + onClosed: false, + + /** + * Defines the callback function for the onDismiss event + * @type {Boolean | Function} + * @default false + */ + onDismiss: false, + + /** + * Defines the callback function for the onMouseenter event + * @type {Boolean | Function} + * @default false + */ + onMouseenter: false, + + /** + * Defines the callback function for the onMouseleave event + * @type {Boolean | Function} + * @default false + */ + onMouseleave: false + + } + + }, + + /** + * Default profile + * @type {Object} + */ + default: { + notification: { + color: '#555' + } + }, + + /** + * Info profile + * @type {Object} + */ + info: { + notification: { + color: '#2574A9' + } + }, + + /** + * Success profile + * @type {Object} + */ + success: { + notification: { + color: '#239D58' + }, + symbol: { + visible: true + } + }, + + /** + * Error profile + * @type {Object} + */ + error: { + notification: { + color: '#B9493E' + }, + symbol: { + visible: true + } + }, + + /** + * Warning profile + * @type {Object} + */ + warning: { + notification: { + color: '#C7932F' + } + } + + }; + + /** + * Get profile options + * + * @param {String} profile Profile name + * @return {Object} Profile options + */ + var get = function get( profile ) { + return list[ profile ]; + }; + + /** + * Check if profile exists + * + * @param {Stirng} profile Profile name + * @return {Boolean} Result + */ + var check = function check( profile ) { + return list.hasOwnProperty( profile ); + }; + + /** + * Configure profile + * + * @param {String} profile Profile name + * @param {Object} options Profile options + */ + var config = function config( profile, options ) { + + for ( var optionGroup in options ) { + + // Create section first (if necessary) + if ( !list[ profile ].hasOwnProperty( optionGroup ) ) { + list[ profile ][ optionGroup ] = {}; + } + + // Create or update each option within this section + for ( var option in options[ optionGroup ] ) { + list[ profile ][ optionGroup ][ option ] = options[ optionGroup ][ option ]; + } + + } + + }; + + /** + * Add new profile + * + * @param {Stirng} profile Profile name + * @param {Object} [options] Profile options + */ + var add = function add( profile, options ) { + list[ profile ] = typeof options !== 'undefined' ? options : {}; + }; + + /** + * Remove profile + * + * @param {String} profile Profile name + */ + var remove = function remove( profile ) { + delete list[ profile ]; + }; + + /** + * Reset profile + * + * @param {String} profile Profile name + */ + var reset = function reset( profile ) { + list[ profile ] = {}; + }; + + /** + * Combine options + * + * Here we combine all existing options (coming from different hierarchies) into one custom options object. The + * options object passed in when calling the 'notify' method is the most specific one and gets the highest + * priority. After that the profile specific options and then the globally defined options are taken into + * account. + * + * @param {Profile} profile Profile name + * @param {Object} options Custom options object + * @return {Object} Combined options object + */ + var combine = function combine( profile, options ) { + + var combinedOptions = {}; + + // Merge all options + for ( var option in list.global ) { + combinedOptions[ option ] = merge( + list.global[ option ], + list[ profile ][ option ], + options[ option ] + ); + } + + return combinedOptions; + + }; + + /** + * Helper: Merge multiple objects + * + * The objects are merged from right to left - that means the rightmost object has the highest priority and the + * lestmost object the lowest priority. The number of passed in objects is variable. + * + * @param {...Object} var_args Multiple option objects + * @return {Object} Merged result object + */ + var merge = function merge() { + + var countArguments = arguments.length; + var result = {}; + + // Clean arguments from useless option objects + for ( var i = countArguments - 1; i > 0; i-- ) { + if ( typeof arguments[ i ] === 'undefined' ) { + delete arguments[ i ]; + } + } + + // Iterate through all available options (globally defined) + for ( var option in arguments[ 0 ] ) { + + // Iterate through the complete option hierarchy + for ( var j = countArguments - 1; j >= 0; j-- ) { + + // If the most specific option has been found, set it and continue with the next option + if ( typeof arguments[ j ] !== 'undefined' && arguments[ j ].hasOwnProperty( option ) ) { + result[ option ] = arguments[ j ][ option ]; + break; + } + + } + + } + + // Done + return result; + + }; + + /** + * Public API + */ + var API = { + get: get, + check: check, + config: config, + add: add, + remove: remove, + reset: reset, + combine: combine + }; + + return API; + + } )(); + + /* ========== NOTIFICATION ========== */ + + /** + * Notification object + * + * Each notification gets its own object in order to allow customized options and actions for each of them. + * + * @param {String} profile Notification profile name + * @param {String} message Notification message + * @param {Object} [options] Custom notification options + */ + var Notification = function Notification( profile, message, options ) { + + /** + * Notification profile name (defaults to 'default') + * @type {String} + */ + this.profile = ( typeof Profiles.get( profile ) !== 'undefined' ) ? profile : 'default'; + + /** + * Notification message + * @type {String} + */ + this.message = message; + + /** + * Custom notification options + * @type {Object} + */ + this.options = ( typeof options !== 'undefined' ) ? options : {}; + + /** + * DOM references to every single component of this notification + * @type {Object} + */ + this.$components = {}; + + // Initialize notification + this.initialize(); + + }; + + /** + * All existing notification instances (static) + * @type {Array} + */ + Notification.instances = []; + + /** + * Global notification events (constant) + * @type {Object} + */ + Notification.prototype.EVENTS = { + + // Event triggers when notification starts animating in + onOpen: new CustomEvent( 'notification.open' ), + + // Event triggers when notification is fully animated in + onOpened: new CustomEvent( 'notification.opened' ), + + // Event triggers when notification start animating out + onClose: new CustomEvent( 'notification.close' ), + + // Event triggers when notification is fully animated out + onClosed: new CustomEvent( 'notification.closed' ), + + // Event triggers when notification is being manually dismissed + onDismiss: new CustomEvent( 'notification.dismiss' ), + + // Event triggers when mouse enters notification area + onMouseenter: new CustomEvent( 'notification.mouseenter' ), + + // Event triggers when mouse leaves notification area + onMouseleave: new CustomEvent( 'notification.mouseleave' ) + + }; + + /** + * Initialize notification + */ + Notification.prototype.initialize = function initialize() { + + var _this = this; + + // Combine multiple option objects into one (respecting the hierarchy) + _this.options = Profiles.combine( _this.profile, _this.options ); + + // If resource is not set, select default symbol or disable symbol visibility + if ( !_this.options.symbol.resource ) { + switch ( _this.profile ) { + case 'success': + _this.options.symbol.resource = icons.success; + break; + case 'error': + _this.options.symbol.resource = icons.error; + break; + default: + _this.options.symbol.visible = false; + } + } + + }; + + /** + * Build notification DOM element + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.build = function build( callback ) { + + var _this = this; + + // DOM elements + var $fragment = document.createDocumentFragment(); + + // Options + var notificationOptions = _this.options.notification; + var symbolOptions = _this.options.symbol; + var messageOptions = _this.options.message; + var buttonOptions = _this.options.dismiss; + var behaviourOptions = _this.options.behaviour; + + // Build container with background + _this.$components.notification = buildContainer(); + _this.$components.background = buildBackground(); + _this.$components.notification.appendChild( _this.$components.background ); + + // Build symbol + if ( symbolOptions.visible ) { + _this.$components.symbol = buildSymbol(); + _this.$components.notification.appendChild( _this.$components.symbol ); + } + + // Build message + if ( messageOptions.visible ) { + _this.$components.message = buildMessage(); + _this.$components.notification.appendChild( _this.$components.message ); + } + + // Build dismiss button + if ( buttonOptions.visible ) { + _this.$components.button = buildButton(); + _this.$components.notification.appendChild( _this.$components.button ); + } + + // Insert notification into DOM + $fragment.appendChild( _this.$components.notification ); + document.body.appendChild( $fragment ); + + // Calculate the required room for an textual-based dismiss button + if ( buttonOptions.visible && buttonOptions.text ) { + _this.$components.notification.style.paddingRight = _this.$components.button.offsetWidth + 'px'; + } + + // Continue + _this.next( callback ); + + /** + * SPECIALIZED BUILD METHODS + */ + + /** + * Build notification container + * + * @return {Element} DOM element + */ + function buildContainer() { + + // Create DOM elements + var $container = document.createElement( 'div' ); + + // Get options + var xPosition = notificationOptions.position[ 0 ]; + var yPosition = notificationOptions.position[ 1 ]; + var xDistance = notificationOptions.distances[ 0 ]; + var yDistance = notificationOptions.distances[ 1 ]; + var size = notificationOptions.height; + var corners = notificationOptions.roundCorners; + + // Set styles + switch ( xPosition ) { + case 'left': + $container.style.left = xDistance + 'px'; + $container.style.marginRight = xDistance + 'px'; + break; + case 'right': + $container.style.right = xDistance + 'px'; + $container.style.marginLeft = xDistance + 'px'; + break; + case 'middle': + $container.style.left = '50%'; + $container.style.webkitTransform = 'translateX(-50%)'; + $container.style.transform = 'translateX(-50%)'; + break; + } + switch ( yPosition ) { + case 'top': + $container.style.top = yDistance + 'px'; + break; + case 'bottom': + $container.style.bottom = yDistance + 'px'; + break; + } + if ( symbolOptions.visible ) { + $container.style.paddingLeft = size + 'px'; + } + if ( corners && ( corners[ 0 ] + corners[ 1 ] + corners[ 2 ] + corners[ 3 ] ) > 0 ) { + $container.style.borderRadius = corners.join( 'px ' ) + 'px'; + } + if ( buttonOptions.visible && !buttonOptions.text ) { + $container.style.paddingRight = ( size - 20 ) + 'px'; + } + if ( notificationOptions.maxWidth ) { + $container.style.maxWidth = notificationOptions.maxWidth + 'px'; + } + + // Set classes + $container.classList.add( 'notification' ); + $container.classList.add( 'notification-' + _this.profile ); + + // Done + return $container; + + } + + /** + * Build notification background + * + * @return {Element} DOM element + */ + function buildBackground() { + + // Create DOM elements + var $background = document.createElement( 'div' ); + + // Get options + var extraSpace = notificationOptions.height / 2; + var color = notificationOptions.color; + + // Set styles + $background.style.left = 'calc(-100% + ' + extraSpace + 'px)'; + $background.style.backgroundColor = color; + + // Set classes + $background.classList.add( 'notification-background' ); + + // Done + return $background; + + } + + /** + * Build notification symbol + * + * @return {Element} DOM element + */ + function buildSymbol() { + + // Create DOM elements + var $symbol; + + // Get options + var size = notificationOptions.height; + var resource = symbolOptions.resource; + var corners = symbolOptions.roundCorners; + var color = symbolOptions.color; + + // Check if resource is an svg or image element + if ( resource.match( /^.*<\/svg>$/i ) !== null || typeof resource === 'function' ) { + + // Check if svg comes from string or function + if ( typeof resource === 'function' ) { + + // Get svg symbol + $symbol = resource(); + + // Verify that the function returned a valid svg DOM element for real + if ( typeof $symbol === 'undefined' || $symbol.nodeName.toLowerCase() !== 'svg' ) { + throw new Error( 'The custom notification symbol is not valid svg.' ); + } + + } else { + + // Get svg symbol (parser does not throw any errors !!) + $symbol = new DOMParser().parseFromString( resource, 'image/svg+xml' ).childNodes[ 0 ]; + + } + + // Set attributes + $symbol.setAttributeNS( null, 'width', '24' ); + $symbol.setAttributeNS( null, 'height', '24' ); + + // Set styles + if ( corners && ( corners[ 0 ] + corners[ 1 ] + corners[ 2 ] + corners[ 3 ] ) > 0 ) { + $symbol.style.padding = ( size / 2 - 17 ) + 'px'; + } else { + $symbol.style.padding = ( size / 2 - 12 ) + 'px'; + } + if ( color ) { + $symbol.style.backgroundColor = color; + } + + } else { + + // Create DOM elements + // We do not use an image object in order to allow positioning and resizing the image dynamically + $symbol = document.createElement( 'div' ); + + // Set background image + $symbol.style.backgroundImage = 'url("' + resource + '")'; + $symbol.style.backgroundPosition = 'center'; + $symbol.style.backgroundSize = 'cover'; + $symbol.style.backgroundRepeat = 'no-repeat'; + + } + + // Set styles + if ( corners && ( corners[ 0 ] + corners[ 1 ] + corners[ 2 ] + corners[ 3 ] ) > 0 ) { + $symbol.style.left = '5px'; + $symbol.style.height = ( size - 10 ) + 'px'; + $symbol.style.width = ( size - 10 ) + 'px'; + $symbol.style.borderRadius = corners.join( 'px ' ) + 'px'; + } else { + $symbol.style.left = '0'; + $symbol.style.height = '100%'; + $symbol.style.width = size + 'px'; + } + + // Set classes (via attribute because IE is too dumb to use classList on SVG elements) + $symbol.setAttributeNS( null, 'class', 'notification-symbol notification-symbol-' + _this.profile ); + + // Done + return $symbol; + + } + + /** + * Build notification message + * + * @return {Element} DOM element + */ + function buildMessage() { + + // DOM elements + var $message = document.createElement( 'p' ); + + // Options + var color = messageOptions.color; + var textSize = messageOptions.textSize; + var textHeight = Math.round( textSize * 1.7 ); + var horizontalPadding = Math.round( notificationOptions.height / 2.75 ); + var verticalPadding = Math.round( ( notificationOptions.height - textHeight ) / 2 ); + + // Set message + // When the html mode is enabled, custom html markup (like hyperlinks or text formatting) will be + // recognized and rendered by the browser. But note that disabling the html mode generally gives you + // improved performance as well as better security (XSS not possible). + if ( behaviourOptions.htmlMode ) { + $message.innerHTML = _this.message; + } else { + $message.textContent = _this.message; + } + + // Set styles + $message.style.color = color; + $message.style.fontSize = textSize + 'px'; + $message.style.lineHeight = textHeight + 'px'; + $message.style.padding = verticalPadding + 'px ' + horizontalPadding + 'px'; + + // Set class + $message.classList.add( 'notification-message' ); + + // Done + return $message; + + } + + /** + * Build notification dismiss button + * + * @return {Element} DOM element + */ + function buildButton() { + + // Create DOM elements + var $button = document.createElement( 'button' ); + + // Get options + var size = notificationOptions.height; + var color = buttonOptions.color; + var text = buttonOptions.text; + + // Set attributes (for accessibility reasons) + $button.setAttribute( 'type', 'button' ); + $button.setAttribute( 'title', 'dismiss this notification' ); + + // Set styles + $button.style.height = ( size - 20 ) + 'px'; + $button.style.padding = ( size / 2 - 18 ) + 'px'; + + // Set icon or text as the button content + if ( text ) { + + // Add text to the button + $button.textContent = text; + + // Set styles + $button.style.color = color; + $button.style.fontSize = '12px'; + $button.style.lineHeight = '16px'; + + } else { + + // Get svg icon (parser does not throw any errors !!) + var $icon = new DOMParser().parseFromString( icons.error, 'image/svg+xml' ).childNodes[ 0 ]; + + // Set attributes + $icon.setAttributeNS( null, 'width', '16' ); + $icon.setAttributeNS( null, 'height', '16' ); + + // Add icon to the button + $button.appendChild( $icon ); + + // Set styles + $button.style.width = ( size - 20 ) + 'px'; + + } + + // Set class + $button.classList.add( 'notification-btn' ); + + // Done + return $button; + + } + + }; + + /** + * Prepare for the next notification + * + * Before showing a new notification we may have to manipulate one or more previously opened notifications first. + * So in the case that stacking is enabled, we need to take the limit setting into account. Is the limit dynamic + * (which means it depends on the current screen height), we first calculate the overall required height, including + * the necessary room for the new notification. Is the limit a static value, we check if the number of allowed + * notifications is already reached. Based on one of these results we may need to close the oldest notification + * first and then shift the others. If stacking is disabled, we only have to close the current notification. + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.prepare = function prepare( callback ) { + + var _this = this; + + // We only need to prepare when at least one notification is already open + if ( Notification.instances.length ) { + + // Stack notifications if option is enabled + if ( _this.options.behaviour.stacking ) { + + var closeFirst; + var limit = _this.options.behaviour.limit; + var countNotifications = Notification.instances.length; + + // Get shift distance depending on the height of the new notification + var shiftDistance = _this.$components.notification.offsetHeight; + + // If the limit is dynamic, make some calculations first + if ( limit === true || limit < 0 ) { + + // Options + var notificationOptions = _this.options.notification; + var size = notificationOptions.height; + var yDistance = notificationOptions.distances[ 1 ]; + var yGap = notificationOptions.distances[ 2 ]; + + // Calculate required overall height + // For this we need to take the height of our new notification, add all the distances (vertical + // distances to screen as well as all gaps between notifications) and combine that with the sum of + // all existing (dynamic) notification heights + var requiredHeight = shiftDistance + yDistance * 2 + countNotifications * yGap; + Notification.instances.forEach( function( element ) { + requiredHeight += element.$components.notification.offsetHeight; + } ); + + // Add additional space when limit is a negative value + if ( limit < 0 ) { + requiredHeight -= ( size + yGap ) * limit; + } + + // Based on the results we finally check if there is enough room for the new notification + closeFirst = ( requiredHeight > window.innerHeight ) ? true : false; + + } else { + + // Check if the limit is set and already reached + closeFirst = ( limit !== false && limit !== 0 && countNotifications === limit ) ? true : false; + + } + + if ( closeFirst ) { + + // Close oldest notification first + Notification.instances[ 0 ].close( function() { + + // Shift notifications + _this.shift( Notification.instances, 'up', shiftDistance, function() { + + // Continue + _this.next( callback ); + + } ); + } ); + + } else { + + // Shift notifications + _this.shift( Notification.instances, 'up', shiftDistance, function() { + + // Continue + _this.next( callback ); + + } ); + + } + + } else { + + // Close oldest notification first + Notification.instances[ 0 ].close( function() { + + // Continue + _this.next( callback ); + + } ); + + } + + } else { + + // Continue + _this.next( callback ); + + } + + }; + + /** + * Open notification + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.open = function open( callback ) { + + var _this = this; + + // Add this notification object to the list of instances + Notification.instances.push( _this ); + + // Trigger 'open' event + document.dispatchEvent( _this.EVENTS.onOpen ); + if ( typeof _this.options.callbacks.onOpen === 'function' ) { + _this.options.callbacks.onOpen(); + } + + // Animate notification in + _this.animate( 'in', function() { + + // Trigger 'opened' event + document.dispatchEvent( _this.EVENTS.onOpened ); + if ( typeof _this.options.callbacks.onOpened === 'function' ) { + _this.options.callbacks.onOpened(); + } + + // Continue + _this.next( callback ); + + } ); + + }; + + /** + * Wait while the notification is open + * + * When the notification is finally visible, we start (if enabled) a countdown after which the notification will be + * closed automatically. Moreover we configure event listeners that allow pausing and resuming this countdown + * temporarily on mouseover and mouseleave, and we also make the dismiss button work. + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.wait = function wait( callback ) { + + var _this = this; + + // Configure the countdown + if ( _this.options.behaviour.autoHide ) { + + // Start the countdown + var countdown = new Countdown( _this.options.behaviour.autoHide * 1000, function() { + + // Continue + _this.next( callback ); + + } ); + + // Configure the countdown pause and resume functionality + if ( _this.options.behaviour.onMouseover ) { + + // Pause countdown when entering the notification area + _this.$components.notification.addEventListener( 'mouseenter', function pause() { + + // Trigger 'mouseenter' event + document.dispatchEvent( _this.EVENTS.onMouseenter ); + if ( typeof _this.options.callbacks.onMouseenter === 'function' ) { + _this.options.callbacks.onMouseenter(); + } + + // Pause / reset countdown + switch ( _this.options.behaviour.onMouseover ) { + case 'pause': + countdown.pause(); + break; + case 'reset': + countdown.stop(); + break; + } + + } ); + + // Resume countdown when leaving the notification area + _this.$components.notification.addEventListener( 'mouseleave', function resume() { + + // Trigger 'mouseleave' event + document.dispatchEvent( _this.EVENTS.onMouseleave ); + if ( typeof _this.options.callbacks.onMouseleave === 'function' ) { + _this.options.callbacks.onMouseleave(); + } + + // Resume / restart countdown + countdown.resume(); + + } ); + + } + + } + + // Configure the dismiss button behaviour + if ( _this.options.dismiss.visible ) { + + // Dismiss notification when clicking the dismiss button + _this.$components.button.addEventListener( 'click', function dismiss( event ) { + + // Remove event listener (just to be sure it's gone and no longer somewhere in memory) + event.target.removeEventListener( event.type, dismiss ); + + // Trigger 'dismiss' event + document.dispatchEvent( _this.EVENTS.onDismiss ); + if ( typeof _this.options.callbacks.onDismiss === 'function' ) { + _this.options.callbacks.onDismiss(); + } + + // Stop countdown (if auto hide is enabled) + if ( _this.options.behaviour.autoHide ) { + countdown.stop(); + } + + // Continue + _this.next( callback ); + + } ); + + } + + }; + + /** + * Close notification + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.close = function close( callback ) { + + var _this = this; + + // Trigger 'close' event + document.dispatchEvent( _this.EVENTS.onClose ); + if ( typeof _this.options.callbacks.onClose === 'function' ) { + _this.options.callbacks.onClose(); + } + + // Animate notification out + _this.animate( 'out', function() { + + // Trigger 'closed' event + document.dispatchEvent( _this.EVENTS.onClosed ); + if ( typeof _this.options.callbacks.onClosed === 'function' ) { + _this.options.callbacks.onClosed(); + } + + // Shift previously opened notifications to get rid of ugly spacing + if ( _this.options.behaviour.stacking && ( Notification.instances.length - 1 ) ) { + + // Get the position of the current notification within the list of instances + var position = Notification.instances.indexOf( _this ); + + // Get shift distance depending on the height of the closed notification + var shiftDistance = _this.$components.notification.offsetHeight; + + // Shift all previous notifications + _this.shift( Notification.instances.slice( 0, position ), 'down', shiftDistance, function() { + + // Remove this notification from the list of instances + Notification.instances.splice( position, 1 ); + + // Continue + _this.next( callback ); + + } ); + + } else { + + // Just clear the list of instances instead of explicitly removing the notification + Notification.instances = []; + + // Continue + _this.next( callback ); + + } + + // Remove this notification from the DOM + document.body.removeChild( _this.$components.notification ); + + } ); + + }; + + /** + * Animate notification in / out + * + * Before animating in or out the notification by setting or removing the 'is-visible' class, we first have to set + * the css transitions and their custom durations and easings. Because we cannot be 100% sure when exactly the css + * transition is done, we are using an event listener to solve this problem - no 'setTimeout' or similar!! + * Theoratically every single fired css transition will trigger this 'transitionend' event when it is done, but we + * only need the first one to be triggered in order to know that we can continue. + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.animate = function animate( direction, callback ) { + + // Options + var _this = this; + var inDuration = _this.options.animations.duration[ 0 ]; + var outDuration = _this.options.animations.duration[ 1 ]; + var easing = _this.options.animations.easing; + + // Animate the notification in or out depending on the animation direction + switch ( direction ) { + + case 'in': + + // Set css transitions if animations are enabled + if ( _this.options.animations.enabled ) { + + // Notification container + _this.$components.notification.style.transition = + 'box-shadow ' + ( inDuration / 1.5 ) + 's ' + easing[ 0 ] + ' ' + ( inDuration / 1.5 ) + 's, ' + + 'opacity ' + inDuration + 's ' + easing[ 0 ]; + + // Notification background + _this.$components.background.style.transition = + 'transform ' + inDuration + 's ' + easing[ 0 ]; + + // Notification symbol + if ( _this.options.symbol.visible ) { + _this.$components.symbol.style.transition = + 'opacity ' + inDuration + 's ' + easing[ 0 ]; + } + + // Notification message + if ( _this.options.message.visible ) { + _this.$components.message.style.transition = + 'transform ' + inDuration + 's ' + easing[ 0 ] + ', ' + + 'opacity ' + inDuration + 's ' + easing[ 0 ]; + } + + // Notification dismiss button + if ( _this.options.dismiss.visible ) { + _this.$components.button.style.transition = + 'opacity ' + ( inDuration / 1.5 ) + 's ' + easing[ 0 ] + ' ' + ( inDuration / 1.5 ) + 's'; + } + + } + + // Force layout update + forceUpdate(); + + // Animate notification in + _this.$components.notification.classList.add( 'is-visible' ); + + break; + + case 'out': + + // Set css transitions if animations are enabled + if ( _this.options.animations.enabled ) { + + // Notification container + _this.$components.notification.style.transition = + 'box-shadow ' + outDuration + 's ' + easing[ 1 ] + ', ' + + 'opacity ' + outDuration + 's ' + easing[ 1 ]; + + // Notification background + _this.$components.background.style.transition = + 'transform 0s linear ' + outDuration + 's'; + + // Notification symbol + if ( _this.options.symbol.visible ) { + _this.$components.symbol.style.transition = + 'opacity 0s linear ' + outDuration + 's'; + } + + // Notification message + if ( _this.options.message.visible ) { + _this.$components.message.style.transition = + 'transform 0s linear ' + outDuration + 's, ' + + 'opacity 0s linear ' + outDuration + 's'; + } + + // Notification dismiss button + if ( _this.options.dismiss.visible ) { + _this.$components.button.style.transition = + 'opacity 0s linear ' + outDuration + 's'; + } + + } + + // Force layout update + forceUpdate(); + + // Animate notification out + _this.$components.notification.classList.remove( 'is-visible' ); + + break; + + } + + if ( _this.options.animations.enabled ) { + + // Check when the css transition is done + _this.$components.notification.addEventListener( 'transitionend', function finished() { + + // Remove event listener immediately + _this.$components.notification.removeEventListener( 'transitionend', finished ); + + // Continue + _this.next( callback ); + + } ); + + } else { + + // Continue + _this.next( callback ); + + } + + /** + * HELPER METHODS + */ + + /** + * This function forces the browser to update the website layout by flushing all pending changes within the + * browsers rendering queue. This is being done by calculating a random (here width) layout property. Only this + * way we can be sure that our DOM element is being fully created and styled, and animating the notification in + * or out by adding or removing the 'is-visible' class works properly. + */ + function forceUpdate() { + return _this.$components.notification.offsetWidth; + } + + }; + + /** + * Shift notifications up / down + * + * In order to allow stacking multiple notifications as well as getting rid of unnecessary and ugly spacing between + * notifications we need to be able to shift some notifications in different directions. The direction of the + * shift movement depends not only on the notification position on the screen itself, but also on whether we make + * room for a new notification or try to remove some spacing resulting from closing another notification, in most + * cases manually. + * Technically because of the highly dynamic situation here we cannot make use of our lovely css transitions or + * animations. Instead we need to use the requestAnimationFrame API in order to create our own custom animations, + * including awesome 60 FPS smoothness. + * + * @param {Array} instances List of notification objects we want to shift + * @param {String} shiftDirection Shifting direction + * @param {Number} shiftDistance Shifting distance + * @param {Function} [callback] Callback function + */ + Notification.prototype.shift = function shift( instances, shiftDirection, shiftDistance, callback ) { + + var _this = this; + + // Only shift when there are notifications to shift + if ( instances.length ) { + + // Get values + var start = new Date(); + var countSelectedNotifications = instances.length; + var countAllNotifications = Notification.instances.length; + var yGap = _this.options.notification.distances[ 2 ]; + + // Get the translateX basis for the case that the horizontal position is set to middle + var translateX = ( _this.options.notification.position[ 0 ] === 'middle' ) ? '-50%' : '0'; + + // Get stacking direction depending on vertical position + var stackDirection = ( _this.options.notification.position[ 1 ] === 'top' ) ? '+' : '-'; + + // Cache the height of each existing notification upfront (for performance reasons) + var notificationHeights = []; + for ( var i = countAllNotifications - 1; i >= 0; i-- ) { + notificationHeights[ i ] = Notification.instances[ i ].$components.notification.offsetHeight; + } + + // Start animation + requestAnimationFrame( render ); + + } else { + + // Continue + _this.next( callback ); + + } + + /** + * 60 FPS animation render loop + * + * First we need to calculate the current animation progress as a decimal value. For this we take the time + * difference between now and the animation start time and divide it by the overall animation duration. If + * (based on the resulting progress) the animation is not finished yet, we draw the next frame - else we render + * the notification in its final end position in order to avoid inaccurate, comma-based pixel values. + */ + function render() { + + // Calculate current animation progress + var animations = _this.options.animations; + var now = new Date(); + var progress = ( animations.enabled ) ? ( now - start ) / ( animations.duration[ 0 ] * 1000 / 1.5 ) : 1; + + // Animation loop + if ( progress < 1 ) { + drawFrame( convertLinearToEase( progress ) ); + requestAnimationFrame( render ); + } else { + drawFrame( 1 ); + _this.next( callback ); + } + + } + + /** + * Draw next frame in which the notification gets its new position + * + * @param {Number} progress Animation progress + */ + function drawFrame( progress ) { + + var basePosition; + var distance; + var newPosition; + + for ( var i = countSelectedNotifications - 1; i >= 0; i-- ) { + + // Calculate base position + if ( i !== countSelectedNotifications - 1 ) { + basePosition += notificationHeights[ i + 1 ] + yGap; + } else { + if ( shiftDirection === 'up' ) { + basePosition = 0; + } else { + basePosition = shiftDistance + yGap; + for ( var j = countAllNotifications - 1; j > countSelectedNotifications; j-- ) { + basePosition += notificationHeights[ j ] + yGap; + } + } + } + + // Calculate new vertical position + distance = basePosition + progress * ( shiftDistance + yGap ) * ( shiftDirection === 'up' ? 1 : -1 ); + + // Update position + newPosition = 'translate(' + translateX + ', ' + stackDirection + distance + 'px)'; + Notification.instances[ i ].$components.notification.style.webkitTransform = newPosition; + Notification.instances[ i ].$components.notification.style.transform = newPosition; + + } + + } + + /** + * Calculate linear value to eased value + * + * @param {Number} t Linear progress number + * @return {Number} Eased progress number (ease-in-out-quad) + */ + function convertLinearToEase( t ) { + return ( t < 0.5 ) ? ( 2 * t * t ) : ( -1 + ( 4 - 2 * t ) * t ); + } + + }; + + /** + * Small helper function that calls a given callback only if it exists + * + * @param {Function} [callback] Callback function + */ + Notification.prototype.next = function next( callback ) { + if ( typeof callback === 'function' ) { + callback(); + } + }; + + /* ========== COUNTDOWN ========== */ + + /** + * Countdown + * + * This countdown offers the ability to be paused, resumed and stopped at any time. + * + * @param {Number} duration Countdown duration in ms + * @param {Function} callback Callback function that is being executed when countdown has finished + */ + var Countdown = function Countdown( duration, callback ) { + + // Options + this.duration = duration; + this.remaining = duration; + this.callback = callback; + + // Start countdown automatically when creating a countdown object + this.resume(); + + }; + + /** + * Start / resume countdown + */ + Countdown.prototype.resume = function resume() { + this.now = new Date(); + this.timerId = window.setTimeout( this.callback, this.remaining ); + }; + + /** + * Pause countdown + */ + Countdown.prototype.pause = function pause() { + window.clearTimeout( this.timerId ); + this.remaining -= new Date() - this.now; + }; + + /** + * Stop countdown + */ + Countdown.prototype.stop = function stop() { + window.clearTimeout( this.timerId ); + this.remaining = this.duration; + }; + + /* ========== PUBLIC API ========== */ + + /** + * Public API + */ + var API = { + + /** + * Show notification + * + * For sure the following construction of nested function calls seems to be a good example for a callback hell. + * But this way we can provide an easy to understand and maintainable overview over the typical life cycle + * procedure of a notification. In order to make it less ugly we simplified all the methods here as much as + * possible (e.g. short names and the callback function as the one and only parameter). + * + * Important note: + * This callback hell is just temporarily and obviously not the best solution. In the near future this part will + * be completely rewritten - based on the technology of JavaScript Promises - as soon as ES6 is supported by all + * major browsers. + * + * @param {String} profile Notification profile name + * @param {String} message Notification message + * @param {Object} [options] Custom notification options + */ + notify: function notify( profile, message, options ) { + + // Create and initialize notification + var notification = new Notification( profile, message, options ); + + // Go! - Start the notification life cycle + notification.build( function() { + notification.prepare( function() { + notification.open( function() { + notification.wait( function() { + notification.close(); + } ); + } ); + } ); + } ); + + }, + + /** + * Register a global event listener (works for all notifications) + * + * @param {String} event Event name + * @param {Function} callback Callback function + */ + on: function on( event, callback ) { + + if ( typeof event === 'undefined' || typeof callback === 'undefined' ) { + throw new Error( 'Adding an event listener requires an event name and a callback function.' ); + } else if ( Notification.EVENTS.indexOf( event ) === -1 ) { + throw new Error( 'An event with the name <' + event + '> does not exist.' ); + } else { + document.addEventListener( 'notification.' + event, callback ); + } + + }, + + /** + * Remove a global event listener (works for all notifications) + * + * @param {String} event Event name + * @param {Function} callback Callback function + */ + off: function off( event, callback ) { + + if ( typeof event === 'undefined' || typeof callback === 'undefined' ) { + throw new Error( 'Removing an event listener requires an event name and a callback function.' ); + } else if ( Notification.EVENTS.indexOf( event ) === -1 ) { + throw new Error( 'An event with the name <' + event + '> does not exist.' ); + } else { + document.removeEventListener( 'notification.' + event, callback ); + } + + }, + + /** + * Clear all existing notifications + * + * @param {Boolean | Number} [offset] Time offset between notifications animating out (in s) + * @param {Function} [callback] Callback function + */ + clearAll: function clearAll() { + + // Clear only when at least one notification is open + if ( Notification.instances.length ) { + + var parameters = getParameters( arguments ); + + // Close all notifications + var countNotifications = Notification.instances.length; + for ( var i = countNotifications - 1; i >= 0; i-- ) { + + // Check if animation offset is enabled + if ( parameters.offset ) { + closeNotification( i ); + } else { + if ( !i && typeof parameters.callback !== 'undefined' ) { + Notification.instances[ i ].close( parameters.callback ); + } else { + Notification.instances[ i ].close(); + } + } + + } + + } + + /** + * Get values out of dynamic parameters + * + * @param {Array} input Incoming function arguments + * @return {Object} Parameters + */ + function getParameters( input ) { + + var output = {}; + + // Find out parameters + switch ( input.length ) { + case 0: + output.offset = 0.15; + output.callback = undefined; + break; + case 1: + output.offset = typeof input[ 0 ] === 'function' ? 0.15 : input[ 0 ]; + output.callback = typeof input[ 0 ] === 'function' ? input[ 0 ] : undefined; + break; + default: + output.offset = input[ 0 ]; + output.callback = input[ 1 ]; + } + + return output; + + } + + /** + * Schedule notification animations + * + * @param {Number} instance Number of notification instance + */ + function closeNotification( instance ) { + + // Get current notification instance + var notification = Notification.instances[ instance ]; + + // Schedule animation + setTimeout( function() { + if ( instance === countNotifications - 1 && typeof parameters.callback !== 'undefined' ) { + notification.close( parameters.callback ); + } else { + notification.close(); + } + }, instance * parameters.offset * 1000 ); + + } + + }, + + /** + * Clear oldest notification + * + * @param {Function} [callback] Callback + */ + clearOldest: function clearOldest( callback ) { + + // Clear only when at least one notification is open + if ( Notification.instances.length ) { + + var notification = Notification.instances[ 0 ]; + if ( typeof callback !== 'undefined' ) { + notification.close( callback ); + } else { + notification.close(); + } + + } + + }, + + /** + * Clear newest notification + * + * @param {Function} [callback] Callback + */ + clearNewest: function clearNewest( callback ) { + + // Clear only when at least one notification is open + if ( Notification.instances.length ) { + + var notification = Notification.instances[ Notification.instances.length - 1 ]; + if ( typeof callback !== 'undefined' ) { + notification.close( callback ); + } else { + notification.close(); + } + + } + + }, + + /** + * Get profile options + * + * @param {String} profile Profile name + * @return {Object} Profile options + */ + getProfile: function getProfile( profile ) { + + if ( typeof profile === 'undefined' ) { + throw new Error( 'Getting the options of a notification profile requires a profile name.' ); + } else if ( !Profiles.check( profile ) ) { + throw new Error( 'A notification profile with the name <' + profile + '> does not exist.' ); + } else { + return Profiles.get( profile ); + } + + }, + + /** + * Check if profile exists + * + * @param {Stirng} profile Profile name + * @return {Boolean} Result + */ + checkProfile: function checkProfile( profile ) { + + if ( typeof profile === 'undefined' ) { + throw new Error( 'Checking if a notification profile exists required a profile name.' ); + } else { + return Profiles.check( profile ); + } + + }, + + /** + * Configure profile + * + * @param {String} profile Profile name + * @param {Object} options Profile options + */ + configProfile: function configProfile( profile, options ) { + + if ( typeof profile === 'undefined' || typeof options === 'undefined' ) { + throw new Error( 'Configuring a notification profile requires a profile name and an options object.' ); + } else if ( !Profiles.check( profile ) ) { + throw new Error( 'A notification profile with the name <' + profile + '> does not exist.' ); + } else { + Profiles.config( profile, options ); + } + + }, + + /** + * Add new profile + * + * @param {String} profile Profile name + * @param {Object} [options] Profile options + */ + addProfile: function addProfile( profile, options ) { + + if ( typeof profile === 'undefined' ) { + throw new Error( 'Adding a new notification profile requires at least a profile name.' ); + } else if ( Profiles.check( profile ) ) { + throw new Error( 'A notification profile with the name <' + profile + '> does not exist.' ); + } else { + Profiles.add( profile, options ); + } + + }, + + /** + * Remove profile + * + * @param {String} profile Profile name + */ + removeProfile: function removeProfile( profile ) { + + if ( typeof profile === 'undefined' ) { + throw new Error( 'Removing a notification profile requires a profile name.' ); + } else if ( [ 'global', 'default', 'info', 'success', 'error', 'warning' ].indexOf( profile ) !== -1 ) { + throw new Error( 'The profile <' + profile + '> is locked and cannot be removed.' ); + } else if ( !Profiles.check( profile ) ) { + throw new Error( 'A notification profile with the name <' + profile + '> does not exist.' ); + } else { + Profiles.remove( profile ); + } + + }, + + /** + * Reset profile + * + * @param {Stirng} profile Profile name + */ + resetProfile: function resetProfile( profile ) { + + if ( typeof profile === 'undefined' ) { + throw new Error( 'Resetting a notification profile requires a profile name.' ); + } else if ( [ 'global', 'default', 'info', 'success', 'error', 'warning' ].indexOf( profile ) !== -1 ) { + throw new Error( 'The profile <' + profile + '> is locked and cannot be reset.' ); + } else if ( !Profiles.check( profile ) ) { + throw new Error( 'A notification profile with the name <' + profile + '> does not exist.' ); + } else { + Profiles.reset( profile ); + } + + } + + }; + + return API; + +} ) ); diff --git a/app/lib/serial.js b/app/lib/serial.js new file mode 100644 index 0000000..7dd1acc --- /dev/null +++ b/app/lib/serial.js @@ -0,0 +1,94 @@ +(function(){var e=window.AmCharts;e.AmRectangularChart=e.Class({inherits:e.AmCoordinateChart,construct:function(a){e.AmRectangularChart.base.construct.call(this,a);this.theme=a;this.createEvents("zoomed","changed");this.marginRight=this.marginBottom=this.marginTop=this.marginLeft=20;this.depth3D=this.angle=0;this.plotAreaFillColors="#FFFFFF";this.plotAreaFillAlphas=0;this.plotAreaBorderColor="#000000";this.plotAreaBorderAlpha=0;this.maxZoomFactor=20;this.zoomOutButtonImageSize=19;this.zoomOutButtonImage= +"lens";this.zoomOutText="Show all";this.zoomOutButtonColor="#e5e5e5";this.zoomOutButtonAlpha=0;this.zoomOutButtonRollOverAlpha=1;this.zoomOutButtonPadding=8;this.trendLines=[];this.autoMargins=!0;this.marginsUpdated=!1;this.autoMarginOffset=10;e.applyTheme(this,a,"AmRectangularChart")},initChart:function(){e.AmRectangularChart.base.initChart.call(this);this.updateDxy();!this.marginsUpdated&&this.autoMargins&&(this.resetMargins(),this.drawGraphs=!1);this.processScrollbars();this.updateMargins();this.updatePlotArea(); +this.updateScrollbars();this.updateTrendLines();this.updateChartCursor();this.updateValueAxes();this.scrollbarOnly||this.updateGraphs()},drawChart:function(){e.AmRectangularChart.base.drawChart.call(this);this.drawPlotArea();if(e.ifArray(this.chartData)){var a=this.chartCursor;a&&a.draw()}},resetMargins:function(){var a={},b;if("xy"==this.type){var c=this.xAxes,d=this.yAxes;for(b=0;b=g-c&&(this.marginRight=Math.round(k-g+c),!isNaN(this.minMarginRight)&&this.marginRighth-c&&(this.marginBottom=Math.round(this.marginBottom+b-h+c),!isNaN(this.minMarginBottom)&& +this.marginBottoma&&(d=a);break;case "bottom":a=h.y+h.height;ga&&(b=a)}}return{l:b,t:d,r:c,b:g}},drawZoomOutButton:function(){var a=this;if(!a.zbSet){var b=a.container.set(); +a.zoomButtonSet.push(b);var c=a.color,d=a.fontSize,g=a.zoomOutButtonImageSize,h=a.zoomOutButtonImage.replace(/\.[a-z]*$/i,""),f=e.lang.zoomOutText||a.zoomOutText,l=a.zoomOutButtonColor,k=a.zoomOutButtonAlpha,m=a.zoomOutButtonFontSize,p=a.zoomOutButtonPadding;isNaN(m)||(d=m);(m=a.zoomOutButtonFontColor)&&(c=m);var m=a.zoomOutButton,n;m&&(m.fontSize&&(d=m.fontSize),m.color&&(c=m.color),m.backgroundColor&&(l=m.backgroundColor),isNaN(m.backgroundAlpha)||(a.zoomOutButtonRollOverAlpha=m.backgroundAlpha)); +var r=m=0,r=a.pathToImages;if(h){if(e.isAbsolute(h)||void 0===r)r="";n=a.container.image(r+h+a.extension,0,0,g,g);e.setCN(a,n,"zoom-out-image");b.push(n);n=n.getBBox();m=n.width+5}void 0!==f&&(c=e.text(a.container,f,c,a.fontFamily,d,"start"),e.setCN(a,c,"zoom-out-label"),d=c.getBBox(),r=n?n.height/2-3:d.height/2,c.translate(m,r),b.push(c));n=b.getBBox();c=1;e.isModern||(c=0);l=e.rect(a.container,n.width+2*p+5,n.height+2*p-2,l,1,1,l,c);l.setAttr("opacity",k);l.translate(-p,-p);e.setCN(a,l,"zoom-out-bg"); +b.push(l);l.toBack();a.zbBG=l;n=l.getBBox();b.translate(a.marginLeftReal+a.plotAreaWidth-n.width+p,a.marginTopReal+p);b.hide();b.mouseover(function(){a.rollOverZB()}).mouseout(function(){a.rollOutZB()}).click(function(){a.clickZB()}).touchstart(function(){a.rollOverZB()}).touchend(function(){a.rollOutZB();a.clickZB()});for(k=0;ka&&(a=1);1>b&&(b=1);this.plotAreaWidth=Math.round(a);this.plotAreaHeight=Math.round(b); +this.plotBalloonsSet.translate(c,d)},updateDxy:function(){this.dx=Math.round(this.depth3D*Math.cos(this.angle*Math.PI/180));this.dy=Math.round(-this.depth3D*Math.sin(this.angle*Math.PI/180));this.d3x=Math.round(this.columnSpacing3D*Math.cos(this.angle*Math.PI/180));this.d3y=Math.round(-this.columnSpacing3D*Math.sin(this.angle*Math.PI/180))},updateMargins:function(){var a=this.getTitleHeight();this.titleHeight=a;this.marginTopReal=this.marginTop-this.dy;this.fixMargins&&!this.fixMargins.top&&(this.marginTopReal+= +a);this.marginBottomReal=this.marginBottom;this.marginLeftReal=this.marginLeft;this.marginRightReal=this.marginRight},updateValueAxes:function(){var a=this.valueAxes,b;for(b=0;bd)var g=c,c=d,d=g;this.relativeZoomValueAxes(b,c,d);this.updateAfterValueZoom()}, +updateAfterValueZoom:function(){this.zoomAxesAndGraphs();this.zoomScrollbar()},relativeZoomValueAxes:function(a,b,c){b=e.fitToBounds(b,0,1);c=e.fitToBounds(c,0,1);if(b>c){var d=b;b=c;c=d}var d=1/this.maxZoomFactor,g=e.getDecimals(d)+4;c-bb.min-g*b.step&&(f+=g,g=0);0>=b.max&&0k&&(k=1);h*=k;f*=k;if(!d||c.equalSpacing)h=Math.round(h),f=Math.round(f)}e=this.chartData.length;c=this.lastTime;k=this.firstTime;0>a?d?(e=this.endTime-this.startTime,d=this.startTime+h*g,g=this.endTime+f*g,0=c&&(g=c,d=c-e),this.zoomToDates(new Date(d),new Date(g))):(0=e-1&&(h=f=0),d=this.start+h,g=this.end+f,this.zoomToIndexes(d,g)):d?(e=this.endTime-this.startTime,d=this.startTime-h*g,g=this.endTime- +f*g,0this.start&&(h=f=0),d=this.start-h,g=this.end-f,this.zoomToIndexes(d,g))}},validateData:function(a){this.marginsUpdated=!1;this.zoomOutOnDataUpdate&&!a&&(this.endTime=this.end=this.startTime=this.start=NaN);e.AmSerialChart.base.validateData.call(this)},drawChart:function(){if(0c&&(a=b-c),a!=this.startTime&&b-a>c&&(b=a+c));var d=this.minSelectedTime;if(0l&&(a=l);bl&&(b=l);bthis.firstTime&&(a=!0),this.endTimec&&(a=b-c,d=!0),a!=this.start&&b-a>c&&(b=a+c,d=!0));if(d&&(d=this.chartScrollbar)&&d.dragger){var g=d.dragger.getBBox();d.maxWidth= +g.width;d.maxHeight=g.height}if(a!=this.start||b!=this.end)d=this.chartData.length-1,isNaN(a)&&(a=0,isNaN(c)||(a=d-c)),isNaN(b)&&(b=d),bd&&(b=d),a>d&&(a=d-1),0>a&&(a=0),this.start=a,this.end=b,this.categoryAxis.zoom(a,b),this.zoomAxesAndGraphs(),this.zoomScrollbar(),this.fixCursor(),0!==a||b!=this.chartData.length-1?this.showZB(!0):this.showZB(!1),this.syncGrid(),this.updateColumnsDepth(),this.dispatchIndexZoomEvent()},updateGraphs:function(){e.AmSerialChart.base.updateGraphs.call(this); +var a=this.graphs,b;for(b=0;bb.depth?1:-1},zoomScrollbar:function(){var a=this.chartScrollbar,b=this.categoryAxis;if(a){if(!this.zoomedByScrollbar){var c=a.dragger;c&&c.stop()}this.zoomedByScrollbar=!1;b.parseDates&&!b.equalSpacing?a.timeZoom(this.startTime, +this.endTime):a.zoom(this.start,this.end)}this.zoomValueScrollbar(this.valueScrollbar)},updateTrendLines:function(){var a=this.trendLines,b;for(b=0;bg&&(g=0);e>a.length-1&&(e=a.length-1);var f=g+Math.round((e-g)/2),l=a[f][b];return c==l?f:1>=e-g?d?g:Math.abs(a[g][b]-c)a&&(a=0),b>d-1&&(b=d-1),d=this.categoryAxis,d.parseDates&&!d.equalSpacing?this.zoom(c[a].time,this.getEndTime(c[b].time)):this.zoom(a,b))}},zoomToDates:function(a,b){var c=this.chartData;if(c)if(this.categoryAxis.equalSpacing){var d=this.getClosestIndex(c,"time",a.getTime(),!0,0,c.length);b=e.resetDateToMin(b,this.categoryAxis.minPeriod,1);c=this.getClosestIndex(c,"time",b.getTime(), +!1,0,c.length);this.zoom(d,c)}else this.zoom(a.getTime(),b.getTime())},zoomToCategoryValues:function(a,b){this.chartData&&this.zoom(this.getCategoryIndexByValue(a),this.getCategoryIndexByValue(b))},formatPeriodString:function(a,b){if(b){b.periodDataItem={};b.periodPercentDataItem={};var c=["value","open","low","high","close"],d="value open low high close average sum count".split(" "),g=b.valueAxis,h=this.chartData,f=b.numberFormatter;f||(f=this.nf);for(var l=0;lD||D>C.graph.height)z=NaN}else if(0>D||D>C.graph.width)z=NaN;if(!isNaN(z)){isNaN(w)&&(w=z);y=z;if(isNaN(u)||u>z)u=z;if(isNaN(q)||qC)x=C;if(isNaN(B)|| +Bb&&0===p&&(p=180):0>c&&270==p&&(p=90);this.gradientRotation=p;0===d&&0===e&&(this.cornerRadius=n);this.draw()},draw:function(){var a=this.set;a.clear(); +var b=this.container,c=b.chart,d=this.w,g=this.h,h=this.dx,f=this.dy,l=this.colors,k=this.alpha,m=this.bwidth,p=this.bcolor,n=this.balpha,r=this.gradientRotation,w=this.cornerRadius,y=this.dashLength,u=this.pattern,q=this.topRadius,E=this.bcn,v=l,t=l;"object"==typeof l&&(v=l[0],t=l[l.length-1]);var x,B,A,G,D,C,z,L,M,Q=k;u&&(k=0);var F,H,I,J,K=this.rotate;if(0Math.abs(g)&&(g=0);1>Math.abs(d)&&(d=0);!isNaN(q)&&(0g&&(m=" A"),k+=m+Math.round(d/2-I)+","+Math.round(g-J)+","+Math.round(d/2+I)+","+Math.round(g+J)+",0,"+g+","+d+","+g,k+=" L"+d+",0",k+=m+Math.round(d/ +2+F)+","+Math.round(H)+","+Math.round(d/2-F)+","+Math.round(-H)+","+d+",0,0,0"):(k+="A"+I+","+J+",0,0,0,"+(d-d/2*(1-q))+","+g+"L"+d+",0",k+="A"+F+","+H+",0,0,1,0,0"),F=180),b=b.path(k).attr(l),b.gradient("linearGradient",[v,e.adjustLuminosity(v,-.3),e.adjustLuminosity(v,-.3),v],F),K?b.translate(h/2,0):b.translate(0,f/2)):b=0===g?e.line(b,[0,d],[0,0],p,n,m,y):0===d?e.line(b,[0,0],[0,g],p,n,m,y):0g?[x, +M,B,A,G,D,C,z,L,b]:[z,L,B,A,G,D,x,M,C,b]:K?0g?[x,b,z]:[z,b,x];e.setCN(c,b,E+"front");e.setCN(c,B,E+"back");e.setCN(c,z,E+"top");e.setCN(c,x,E+"bottom");e.setCN(c,G,E+"left");e.setCN(c,D,E+"right");for(x=0;xb&&(this.endTime=b);q=this.minorGridEnabled;y=this.gridAlpha;var x=0,B=0;if(this.widthField)for(b=this.start;b<=this.end;b++)if(t=this.data[b]){var A=Number(this.data[b].dataContext[this.widthField]);isNaN(A)||(x+=A,t.widthValue=A)}if(this.parseDates&&!this.equalSpacing)this.lastTime=a[a.length-1].time,this.maxTime=e.resetDateToMin(new Date(this.lastTime+1.05*r),this.minPeriod, +1,p).getTime(),this.timeDifference=this.endTime-this.startTime,this.parseDatesDraw();else if(!this.parseDates){if(this.cellWidth=this.getStepWidth(f),ff&&(f=0),l=0,this.widthField&&(f=this.start),this.end-f+1>=this.autoRotateCount&&(this.labelRotationR=this.autoRotateAngle),b=f;b<=this.end+2;b++){p=!1;0<=b&&bthis.end&&"start"==this.tickPosition&&(n=" ");this.rotate&&this.inside&&(u-=2);isNaN(w.widthValue)||(w.percentWidthValue=w.widthValue/x*100,A=this.rotate?this.height*w.widthValue/x:this.width*w.widthValue/x,f=B,B+=A,u=A/2);u=new this.axisItemRenderer(this,f,n,p,A,u,void 0,t,a,!1,w.labelColor,w.className);u.serialDataItem=w;this.pushAxisItem(u);this.gridAlpha= +y}}else if(this.parseDates&&this.equalSpacing){h=this.start;this.startTime=this.data[this.start].time;this.endTime=this.data[this.end].time;this.timeDifference=this.endTime-this.startTime;b=this.choosePeriod(0);g=b.period;w=b.count;b=e.getPeriodDuration(g,w);bf&&(f=0);B=this.end+2;B>=this.data.length&&(B=this.data.length);a=!1;a=!k;this.previousPos=-1E3;20=A){f=this.getCoordinate(b-this.start);q=!1;this.nextPeriod[y]&&(q=this.checkPeriodChange(this.nextPeriod[y],1,r,n,y))&&e.resetDateToMin(new Date(r),this.nextPeriod[y],1,p).getTime()!=r&&(q=!1);t=!1;q&&this.markPeriodChange?(q=this.dateFormatsObject[this.nextPeriod[y]],t=!0):q=this.dateFormatsObject[y];n=e.formatDate(new Date(r),q,c);if(b==d&&!k||b==l&&!m)n=" ";a?a=!1:(v||(t=!1),f-this.previousPos>this.safeDistance* +Math.cos(this.labelRotationR*Math.PI/180)&&(this.labelFunction&&(n=this.labelFunction(n,new Date(r),this,g,w,u)),this.boldLabels&&(t=!0),u=new this.axisItemRenderer(this,f,n,void 0,void 0,void 0,void 0,t),q=u.graphics(),this.pushAxisItem(u),q=q.getBBox().width,e.isModern||(q-=f),this.previousPos=f+q));u=n=r}}for(b=k=0;bthis.height+1&&h--:l>this.width+1&&h--;0>l&&h++;return h=e.fitToBounds(h,0,b.length-1)},dateToCoordinate:function(a){return this.parseDates&& +!this.equalSpacing?(a.getTime()-this.startTime)*this.stepWidth:this.parseDates&&this.equalSpacing?(a=this.chart.getClosestIndex(this.data,"time",a.getTime(),!1,0,this.data.length-1),this.getCoordinate(a-this.start)):NaN},categoryToCoordinate:function(a){if(this.chart){if(this.parseDates)return this.dateToCoordinate(new Date(a));a=this.chart.getCategoryIndexByValue(a);if(!isNaN(a))return this.getCoordinate(a-this.start)}else return NaN},coordinateToDate:function(a){return this.equalSpacing?(a=this.xToIndex(a), +new Date(this.data[a].time)):new Date(this.startTime+a/this.stepWidth)},coordinateToValue:function(a){a=this.xToIndex(a);if(a=this.data[a])return this.parseDates?a.time:a.category},getCoordinate:function(a){a*=this.stepWidth;this.startOnAxis||(a+=this.stepWidth/2);return Math.round(a)},formatValue:function(a,b){b||(b=this.currentDateFormat);this.parseDates&&(a=e.formatDate(new Date(a),b,this.chart));return a},showBalloonAt:function(a,b){void 0===b&&(b=this.parseDates?this.dateToCoordinate(new Date(a)): +this.categoryToCoordinate(a));return this.adjustBalloonCoordinate(b)},formatBalloonText:function(a,b,c){var d="",g="",h=this.chart,f=this.data[b];if(f)if(this.parseDates)d=e.formatDate(f.category,c,h),b=e.changeDate(new Date(f.category),this.minPeriod,1),g=e.formatDate(b,c,h),-1!=d.indexOf("fff")&&(d=e.formatMilliseconds(d,f.category),g=e.formatMilliseconds(g,b));else{var l;this.data[b+1]&&(l=this.data[b+1]);d=e.fixNewLines(f.category);l&&(g=e.fixNewLines(l.category))}a=a.replace(/\[\[category\]\]/g, +String(d));return a=a.replace(/\[\[toCategory\]\]/g,String(g))},adjustBalloonCoordinate:function(a,b){var c=this.xToIndex(a),d=this.chart.chartCursor;if(this.stickBalloonToCategory){var e=this.data[c];e&&(a=e.x[this.id]);this.stickBalloonToStart&&(a-=this.cellWidth/2);var h=0;if(d){var f=d.limitToGraph;if(f){var l=f.valueAxis.id;f.hidden||(h=e.axes[l].graphs[f.id].y)}this.rotate?("left"==this.position?(f&&(h-=d.width),0h&&(h=0),d.fixHLine(a,h)):("top"==this.position?(f&&(h-=d.height), +0h&&(h=0),d.fixVLine(a,h))}}d&&!b&&(d.setIndex(c),this.parseDates&&d.setTimestamp(this.coordinateToDate(a).getTime()));return a}})})(); diff --git a/app/test.html b/app/test.html index aebdc10..cff119b 100644 --- a/app/test.html +++ b/app/test.html @@ -5,6 +5,8 @@ Title + + @@ -101,12 +103,12 @@ @@ -170,6 +172,12 @@ Co2 + + + + + + diff --git a/bower.json b/bower.json index 9622781..0ee2d63 100644 --- a/bower.json +++ b/bower.json @@ -21,6 +21,8 @@ "mui": "^0.6.8", "sugarjs-date": "^1.5.1", "backbone": "^1.3.3", - "base64": "^1.0.0" + "base64": "^1.0.0", + "notification-js": "^1.1.1", + "amstock3": "^3.20.12" } } diff --git a/lib/mdot/api.js b/lib/mdot/api.js index 02d33cd..74447c2 100644 --- a/lib/mdot/api.js +++ b/lib/mdot/api.js @@ -1,8 +1,7 @@ var logger = require('log4js').getLogger(); -var util = require('util'); - var mdot = require('./mdot.js'); + module.exports = function(app) { var express = require('express'); var mdotRouter = express.Router(); @@ -12,10 +11,9 @@ module.exports = function(app) { var data = {}; logger.debug('mDot-GetData'); - logger.debug(req); if (!req.params.id) { - logger.error('MDot','Missing required paramter'); + logger.error('MDot','Missing required parameter'); res.status(400).send({ status: 'error', error: 'missing required parameter' @@ -23,21 +21,24 @@ module.exports = function(app) { return; } - logger.debug(req.params.id); + if (req.query.hasOwnProperty('start') && req.query.hasOwnProperty('start')) { + data.start = req.query.start; + data.end = req.query.end; + } if (req.params.hasOwnProperty('id')) { data.id = req.params.id; - logger.debug(data); - - - mdot.doGet() - .then(() => { + mdot.doGet(data) + .then((d) => { 'use strict'; - console.log('outside the promise'); + res.json(d); + }) + .catch((e) => { + logger.error(e); + res.status(500).json({}); }); - //logger.debug(util.inspect(mdot,{showHidden:true})); } }); diff --git a/lib/mdot/apiv2.js b/lib/mdot/apiv2.js new file mode 100644 index 0000000..41ad477 --- /dev/null +++ b/lib/mdot/apiv2.js @@ -0,0 +1,51 @@ +'use strict'; +var logger = require('log4js').getLogger(); +var mdot = require('./mdot.js'); + +var db = require('../server/db-connector').dbConnection; + +var dbMdot = require('../server/db-mdot')(db); + +module.exports = function(app) { + var express = require('express'); + var mdotRouter = express.Router(); + + mdotRouter.get('/:id', function(req, res) { + console.log(req.headers); + + var data = {}; + logger.debug('mDot-GetData'); + + if (!req.params.id) { + logger.error('MDot','Missing required parameter'); + res.status(400).send({ + status: 'error', + error: 'missing required parameter' + }); + return; + } + + if (req.query.hasOwnProperty('start') && req.query.hasOwnProperty('start')) { + data.start = req.query.start; + data.end = req.query.end; + } + + if (req.params.hasOwnProperty('id')) { + data.id = req.params.id; + + dbMdot.doGet(data) + .then((d) => { + res.json({events:d}); + }) + .catch((e) => { + logger.error(e); + res.status(500).json({}); + }); + + } + + }); + + app.use('/apiv2/mdot/', mdotRouter); +}; + diff --git a/lib/mdot/mdot.js b/lib/mdot/mdot.js index d3aa14e..f273a75 100644 --- a/lib/mdot/mdot.js +++ b/lib/mdot/mdot.js @@ -1,15 +1,85 @@ 'use strict'; +var rp = require('request-promise'); var logger = require('log4js').getLogger(); +var util = require('util'); +var request = require('request'); +var btoa = require('btoa'); +var buildAuthentication = function() { + var user = 'a-qz0da4-dfwwdkmkzr'; // Your actual username + var pass = '9txJEf3Cjy7hkSOvkv'; // Your actual password + var token = user.concat(':', pass); + + return 'Basic '.concat(btoa(token)); +}; + +function getData(data, prev) { + return new Promise(function(resolve, reject) { + logger.debug('Working...'); + var range = ''; + + if (data.hasOwnProperty('start')) { + range = ['?start=', data.start, '&end=', data.end].join(''); + } + + if (data.hasOwnProperty('bookmark')) { + + range = range.concat(range.length > 0 ? '&' : '?'); + range = range.concat('_bookmark=',encodeURIComponent(data.bookmark)); + + } + + var options = { + uri: 'https://qz0da4.internetofthings.ibmcloud.com/api/v0002/historian/types/mDot/devices/' + data.id + range, + headers: { + Authorization: buildAuthentication() + }, + json: true + }; + + logger.debug(options); + + // This is put together to get around the Paging that bluemix uses. + // They limit the number of results to 100 per query + // Kind of sloppy using the promises like this I think. + + rp(options) + .then(function(d) { + + prev.events.concat(d.events); + + Array.prototype.push.apply(prev.events, d.events); + + if (d.hasOwnProperty('bookmark')) { + data.bookmark = d.bookmark; + getData(data, prev).then(function(d) { + resolve(d); + }); + } else { + logger.debug('Final resolve...'); + resolve(prev); + } + }) + .catch(function(err) { + logger.error(err); + reject(err); + }); + }); +} var self = module.exports = { - - doGet : function(data) { - + doGet: function(data) { return new Promise(function(resolve, reject) { - logger.debug('doGet Promise'); - resolve(''); + logger.debug('Inside doGet Promise'); + logger.debug('About to query'); + + getData(data,{events: []}).then(function(d) { + resolve(d); + }).catch(function(e) { + logger.error(e); + reject(e); + }); }); } diff --git a/lib/server/db-connector.js b/lib/server/db-connector.js new file mode 100644 index 0000000..7ab3d2c --- /dev/null +++ b/lib/server/db-connector.js @@ -0,0 +1,21 @@ +'uses strict'; +/** + * + * User: Martin Donnelly + * Date: 2016-03-11 + * Time: 10:22 + * + */ + +var pgp = require('pg-promise')(); + +var cn = { + host: 'localhost', + port: 5432, + database: 'mdot', + user: 'postgres', + password: '' +}; + +exports.dbConnection = pgp(cn); + diff --git a/lib/server/db-mdot.js b/lib/server/db-mdot.js new file mode 100644 index 0000000..bc04c65 --- /dev/null +++ b/lib/server/db-mdot.js @@ -0,0 +1,88 @@ +'use strict'; +var logger = require('log4js').getLogger(); + +var Sugar = require('sugar-date'); + + + +module.exports = function(db) { + var module = {}; + module.deviceIds = ['CENSIS-LoRa-1','CENSIS-LoRa-2','CENSIS-LoRa-3','CENSIS-LoRa-4','HIE-mobile-1','HIE-demo','HIE-mobile-2','HIE-smart-campus-1','HIE-smart-campus-2','HIE-smart-campus-3','HIE-smart-campus-4','HIE-smart-campus-5','HIE-smart-campus-6','HIE-smart-campus-7','HIE-mDot-1']; + + + module.sqlAllGetDecoded = function(deviceId) { + logger.debug('mdot:sqlAllGetDecoded'); + return new Promise(function(resolve, reject) { + db.any('select * from decoded where deviceid=$1;', [deviceId]) + .then(function(d) { + return resolve(d); + }) + .catch((err)=> { + logger.error(err); + return reject(err); + }); + }); + }; + + module.sqlRangedGetDecoded = function(params) { + logger.debug('mdot:sqlRangedGetDecoded'); + return new Promise(function(resolve, reject) { + db.any('select * from decoded where deviceid=$1 and timestamp between $2 and $3;', [params.deviceID, params.startTS, params.endTS]) + .then(function(d) { + return resolve(d); + }) + .catch((err)=> { + logger.error(err); + return reject(err); + }); + + }); + + }; + + + + module.doGet = function(params) { + var self = this; + var _obj = {}; + return new Promise(function(resolve, reject) { + logger.debug('mdot.doGet', params); + + _obj.deviceID = module.deviceIds.indexOf(params.id); + if (params.hasOwnProperty('start') && params.hasOwnProperty('start')) { + + logger.debug('params.start',params.start); + logger.debug('params.start',Sugar.Date.create(params.start )); + _obj.startTS = Sugar.Date.create(params.start); + _obj.endTS = Sugar.Date.create(params.end); + + logger.info(_obj); + + self.sqlRangedGetDecoded(_obj) + .then(function(d) { + resolve(d); + }) + .catch(function(e) { + logger.error(e); + reject(e); + }); + + } else { + self.sqlAllGetDecoded(_obj.deviceID) + .then(function(d) { + resolve(d); + }) + .catch(function(e) { + logger.error(e); + reject(e); + }); + } + + + }); + }; + + + return module; +}; + diff --git a/lib/server/db-save.js b/lib/server/db-save.js new file mode 100644 index 0000000..c128bc2 --- /dev/null +++ b/lib/server/db-save.js @@ -0,0 +1,128 @@ +'use strict'; + +var newId = require('uuid-pure').newId; +var atob = require('atob'); + +module.exports = function(db) { + var module = {}; + module.deviceIds = ['CENSIS-LoRa-1','CENSIS-LoRa-2','CENSIS-LoRa-3','CENSIS-LoRa-4','HIE-mobile-1','HIE-demo','HIE-mobile-2','HIE-smart-campus-1','HIE-smart-campus-2','HIE-smart-campus-3','HIE-smart-campus-4','HIE-smart-campus-5','HIE-smart-campus-6','HIE-smart-campus-7','HIE-mDot-1']; + + module.sqlInsertRawEvent = function(data) { + let _data = data; + console.log('sqlInsertRawEvent'); + return new Promise(function(resolve, reject) { + db.func('insert_raw', + [_data.timestamp, _data.event]) + .then(()=> { + return resolve('ok'); + }) + .catch((err)=> { + return reject(err); + }); + }); + }; + + module.sqlInsertDecoded = function(data) { + let _data = data; + + console.log('sqlInsertDecoded', _data.deviceid, _data.timestamp); + return new Promise(function(resolve, reject) { + db.func('insert_decoded', + [_data.deviceid, _data.timestamp, _data.lux, _data.co2, _data.temp, _data.humidity, _data.sound]) + .then(()=> { + return resolve('ok'); + }) + .catch((err)=> { + return reject(err); + }); + }); + + }; + + module.addNewEvent = function(data) { + console.log('addNewEvent'); + var self = this; + return new Promise((resolve, reject) => { + + let _data = {}; + _data.timestamp = new Date(); + _data.event = data; + + self.sqlInsertRawEvent(_data) + .then((d)=> { + console.log('Postgres returns', d); + return resolve({reply: 'raw event inserted'}); + }) + .catch((err)=> { + return reject(err); + }); + }); + }; + + module.addProcessedEvent = function(data) { + console.log('addProcessedEvent'); + var self = this; + return new Promise((resolve, reject) => { + + let _data = self.rawBreaker(data); + + self.sqlInsertDecoded(_data) + .then((d)=> { + console.log('Postgres returns', d); + return resolve({reply: 'Processed event inserted'}); + }) + .catch((err)=> { + return reject(err); + }); + + + }); + + }; + + module.decoder = function(data) { + var _obj = {}; + var _data = atob(data).split(''); + + var bytes = _data.map(i => i.charCodeAt()); + + _obj.light = parseInt('0x' + ('0' + bytes[0]).substr(-2) + ('0' + bytes[1]).substr(-2)); + _obj.co2 = parseInt(_data[2] + _data[3] + _data[4] + _data[5] + _data[6], 10); + _obj.temp = (parseInt(_data[7] + _data[8] + _data[9] + _data[10] + _data[11], 10) - 1000) / 10; + _obj.humid = (parseInt(_data[12] + _data[13] + _data[14] + _data[15] + _data[16], 10) / 10); + _obj.noise = parseInt('0x' + ('0' + bytes[17]).substr(-2) + ('0' + bytes[18]).substr(-2)); + _obj.binData = bytes; + return _obj; + }; + + module.rawBreaker = function(data) { + var self = this; + var workObj = {}; + + var device_name = data.topic.split('/')[4]; + console.log('Device_name', device_name); + workObj.deviceid = self.deviceIds.indexOf(device_name); + + if (data.hasOwnProperty('data')) { + + var _data = self.decoder(data.data); + + workObj.lux = _data.light; + workObj.co2 = _data.co2; + workObj.temp = _data.temp; + workObj.humidity = _data.humid; + workObj.sound = _data.noise; + workObj.timestamp = new Date(); + + return workObj; + } else { + console.error('Data does not have base64 data'); + + return null; + } + + }; + + return module; +}; + diff --git a/package.json b/package.json index 0151fb6..147453b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "body-parser": "^1.15.1", + "btoa": "^1.1.2", "cookie-parser": "*", "ejs": "*", "errorhandler": "*", @@ -20,7 +21,10 @@ "mqtt": "^1.10.0", "mqtt_over_websockets": "0.0.1", "node-localstorage": "^1.1.2", - "request": "^2.72.0", + "pg-promise": "^5.2.7", + "request": "^2.74.0", + "request-promise": "^4.1.1", + "uuid-pure": "^1.0.10", "websocket": "^1.0.22" }, "devDependencies": { @@ -40,7 +44,7 @@ "gulp-cssmin": "^0.1.7", "gulp-cssnano": "^2.1.2", "gulp-debug": "^2.1.2", - "gulp-google-webfonts": "0.0.12", + "gulp-google-webfonts": "0.0.13", "gulp-html-replace": "^1.5.5", "gulp-htmlmin": "^2.0.0", "gulp-inject": "^4.0.0", @@ -51,20 +55,20 @@ "gulp-rename": "^1.2.2", "gulp-size": "^2.1.0", "gulp-strip-debug": "^1.1.0", - "gulp-uglify": "^1.5.3", + "gulp-uglify": "^2.0.0", "jshint": "^2.9.2", "jsonfile": "^2.3.1", "log4js": "^0.6.36", - "mocha": "^2.4.5", + "mocha": "^3.0.2", "mqtt-ws": "^0.2.0", "node-cron": "^1.1.1", "require-dir": "^0.3.0", - "should": "^8.3.1", + "should": "^10.0.0", "string": "^3.3.1", - "sugar": "^1.4.1", - "sugar-date": "^1.5.1", - "superagent": "^1.7.2", - "supertest": "^1.1.0" + "sugar": "^2.0.1", + "sugar-date": "^2.0.0", + "superagent": "^2.1.0", + "supertest": "^2.0.0" }, "scripts": { "test": "mocha --recursive --reporter spec --bail --check-leaks --timeout 3000",