index.html 186 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  5. <title>Repository statistics for 'libreflix'</title>
  6. <script type="application/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  7. <script type="application/javascript">
  8. (function($){$.extend({tablesorter:new
  9. function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
  10. var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[$?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[$]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
  11. </script>
  12. <script type="application/javascript">/* Javascript plotting library for jQuery, version 0.8.3.
  13. Copyright (c) 2007-2014 IOLA and Ole Laursen.
  14. Licensed under the MIT license.
  15. */
  16. // first an inline dependency, jquery.colorhelpers.js, we inline it here
  17. // for convenience
  18. /* Plugin for jQuery for working with colors.
  19. *
  20. * Version 1.1.
  21. *
  22. * Inspiration from jQuery color animation plugin by John Resig.
  23. *
  24. * Released under the MIT license by Ole Laursen, October 2009.
  25. *
  26. * Examples:
  27. *
  28. * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
  29. * var c = $.color.extract($("#mydiv"), 'background-color');
  30. * console.log(c.r, c.g, c.b, c.a);
  31. * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
  32. *
  33. * Note that .scale() and .add() return the same modified object
  34. * instead of making a new one.
  35. *
  36. * V. 1.1: Fix error handling so e.g. parsing an empty string does
  37. * produce a color rather than just crashing.
  38. */
  39. (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
  40. // the actual Flot code
  41. (function($) {
  42. // Cache the prototype hasOwnProperty for faster access
  43. var hasOwnProperty = Object.prototype.hasOwnProperty;
  44. // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM
  45. // operation produces the same effect as detach, i.e. removing the element
  46. // without touching its jQuery data.
  47. // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
  48. if (!$.fn.detach) {
  49. $.fn.detach = function() {
  50. return this.each(function() {
  51. if (this.parentNode) {
  52. this.parentNode.removeChild( this );
  53. }
  54. });
  55. };
  56. }
  57. ///////////////////////////////////////////////////////////////////////////
  58. // The Canvas object is a wrapper around an HTML5 <canvas> tag.
  59. //
  60. // @constructor
  61. // @param {string} cls List of classes to apply to the canvas.
  62. // @param {element} container Element onto which to append the canvas.
  63. //
  64. // Requiring a container is a little iffy, but unfortunately canvas
  65. // operations don't work unless the canvas is attached to the DOM.
  66. function Canvas(cls, container) {
  67. var element = container.children("." + cls)[0];
  68. if (element == null) {
  69. element = document.createElement("canvas");
  70. element.className = cls;
  71. $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
  72. .appendTo(container);
  73. // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
  74. if (!element.getContext) {
  75. if (window.G_vmlCanvasManager) {
  76. element = window.G_vmlCanvasManager.initElement(element);
  77. } else {
  78. throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
  79. }
  80. }
  81. }
  82. this.element = element;
  83. var context = this.context = element.getContext("2d");
  84. // Determine the screen's ratio of physical to device-independent
  85. // pixels. This is the ratio between the canvas width that the browser
  86. // advertises and the number of pixels actually present in that space.
  87. // The iPhone 4, for example, has a device-independent width of 320px,
  88. // but its screen is actually 640px wide. It therefore has a pixel
  89. // ratio of 2, while most normal devices have a ratio of 1.
  90. var devicePixelRatio = window.devicePixelRatio || 1,
  91. backingStoreRatio =
  92. context.webkitBackingStorePixelRatio ||
  93. context.mozBackingStorePixelRatio ||
  94. context.msBackingStorePixelRatio ||
  95. context.oBackingStorePixelRatio ||
  96. context.backingStorePixelRatio || 1;
  97. this.pixelRatio = devicePixelRatio / backingStoreRatio;
  98. // Size the canvas to match the internal dimensions of its container
  99. this.resize(container.width(), container.height());
  100. // Collection of HTML div layers for text overlaid onto the canvas
  101. this.textContainer = null;
  102. this.text = {};
  103. // Cache of text fragments and metrics, so we can avoid expensively
  104. // re-calculating them when the plot is re-rendered in a loop.
  105. this._textCache = {};
  106. }
  107. // Resizes the canvas to the given dimensions.
  108. //
  109. // @param {number} width New width of the canvas, in pixels.
  110. // @param {number} width New height of the canvas, in pixels.
  111. Canvas.prototype.resize = function(width, height) {
  112. if (width <= 0 || height <= 0) {
  113. throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
  114. }
  115. var element = this.element,
  116. context = this.context,
  117. pixelRatio = this.pixelRatio;
  118. // Resize the canvas, increasing its density based on the display's
  119. // pixel ratio; basically giving it more pixels without increasing the
  120. // size of its element, to take advantage of the fact that retina
  121. // displays have that many more pixels in the same advertised space.
  122. // Resizing should reset the state (excanvas seems to be buggy though)
  123. if (this.width != width) {
  124. element.width = width * pixelRatio;
  125. element.style.width = width + "px";
  126. this.width = width;
  127. }
  128. if (this.height != height) {
  129. element.height = height * pixelRatio;
  130. element.style.height = height + "px";
  131. this.height = height;
  132. }
  133. // Save the context, so we can reset in case we get replotted. The
  134. // restore ensure that we're really back at the initial state, and
  135. // should be safe even if we haven't saved the initial state yet.
  136. context.restore();
  137. context.save();
  138. // Scale the coordinate space to match the display density; so even though we
  139. // may have twice as many pixels, we still want lines and other drawing to
  140. // appear at the same size; the extra pixels will just make them crisper.
  141. context.scale(pixelRatio, pixelRatio);
  142. };
  143. // Clears the entire canvas area, not including any overlaid HTML text
  144. Canvas.prototype.clear = function() {
  145. this.context.clearRect(0, 0, this.width, this.height);
  146. };
  147. // Finishes rendering the canvas, including managing the text overlay.
  148. Canvas.prototype.render = function() {
  149. var cache = this._textCache;
  150. // For each text layer, add elements marked as active that haven't
  151. // already been rendered, and remove those that are no longer active.
  152. for (var layerKey in cache) {
  153. if (hasOwnProperty.call(cache, layerKey)) {
  154. var layer = this.getTextLayer(layerKey),
  155. layerCache = cache[layerKey];
  156. layer.hide();
  157. for (var styleKey in layerCache) {
  158. if (hasOwnProperty.call(layerCache, styleKey)) {
  159. var styleCache = layerCache[styleKey];
  160. for (var key in styleCache) {
  161. if (hasOwnProperty.call(styleCache, key)) {
  162. var positions = styleCache[key].positions;
  163. for (var i = 0, position; position = positions[i]; i++) {
  164. if (position.active) {
  165. if (!position.rendered) {
  166. layer.append(position.element);
  167. position.rendered = true;
  168. }
  169. } else {
  170. positions.splice(i--, 1);
  171. if (position.rendered) {
  172. position.element.detach();
  173. }
  174. }
  175. }
  176. if (positions.length == 0) {
  177. delete styleCache[key];
  178. }
  179. }
  180. }
  181. }
  182. }
  183. layer.show();
  184. }
  185. }
  186. };
  187. // Creates (if necessary) and returns the text overlay container.
  188. //
  189. // @param {string} classes String of space-separated CSS classes used to
  190. // uniquely identify the text layer.
  191. // @return {object} The jQuery-wrapped text-layer div.
  192. Canvas.prototype.getTextLayer = function(classes) {
  193. var layer = this.text[classes];
  194. // Create the text layer if it doesn't exist
  195. if (layer == null) {
  196. // Create the text layer container, if it doesn't exist
  197. if (this.textContainer == null) {
  198. this.textContainer = $("<div class='flot-text'></div>")
  199. .css({
  200. position: "absolute",
  201. top: 0,
  202. left: 0,
  203. bottom: 0,
  204. right: 0,
  205. 'font-size': "smaller",
  206. color: "#545454"
  207. })
  208. .insertAfter(this.element);
  209. }
  210. layer = this.text[classes] = $("<div></div>")
  211. .addClass(classes)
  212. .css({
  213. position: "absolute",
  214. top: 0,
  215. left: 0,
  216. bottom: 0,
  217. right: 0
  218. })
  219. .appendTo(this.textContainer);
  220. }
  221. return layer;
  222. };
  223. // Creates (if necessary) and returns a text info object.
  224. //
  225. // The object looks like this:
  226. //
  227. // {
  228. // width: Width of the text's wrapper div.
  229. // height: Height of the text's wrapper div.
  230. // element: The jQuery-wrapped HTML div containing the text.
  231. // positions: Array of positions at which this text is drawn.
  232. // }
  233. //
  234. // The positions array contains objects that look like this:
  235. //
  236. // {
  237. // active: Flag indicating whether the text should be visible.
  238. // rendered: Flag indicating whether the text is currently visible.
  239. // element: The jQuery-wrapped HTML div containing the text.
  240. // x: X coordinate at which to draw the text.
  241. // y: Y coordinate at which to draw the text.
  242. // }
  243. //
  244. // Each position after the first receives a clone of the original element.
  245. //
  246. // The idea is that that the width, height, and general 'identity' of the
  247. // text is constant no matter where it is placed; the placements are a
  248. // secondary property.
  249. //
  250. // Canvas maintains a cache of recently-used text info objects; getTextInfo
  251. // either returns the cached element or creates a new entry.
  252. //
  253. // @param {string} layer A string of space-separated CSS classes uniquely
  254. // identifying the layer containing this text.
  255. // @param {string} text Text string to retrieve info for.
  256. // @param {(string|object)=} font Either a string of space-separated CSS
  257. // classes or a font-spec object, defining the text's font and style.
  258. // @param {number=} angle Angle at which to rotate the text, in degrees.
  259. // Angle is currently unused, it will be implemented in the future.
  260. // @param {number=} width Maximum width of the text before it wraps.
  261. // @return {object} a text info object.
  262. Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
  263. var textStyle, layerCache, styleCache, info;
  264. // Cast the value to a string, in case we were given a number or such
  265. text = "" + text;
  266. // If the font is a font-spec object, generate a CSS font definition
  267. if (typeof font === "object") {
  268. textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
  269. } else {
  270. textStyle = font;
  271. }
  272. // Retrieve (or create) the cache for the text's layer and styles
  273. layerCache = this._textCache[layer];
  274. if (layerCache == null) {
  275. layerCache = this._textCache[layer] = {};
  276. }
  277. styleCache = layerCache[textStyle];
  278. if (styleCache == null) {
  279. styleCache = layerCache[textStyle] = {};
  280. }
  281. info = styleCache[text];
  282. // If we can't find a matching element in our cache, create a new one
  283. if (info == null) {
  284. var element = $("<div></div>").html(text)
  285. .css({
  286. position: "absolute",
  287. 'max-width': width,
  288. top: -9999
  289. })
  290. .appendTo(this.getTextLayer(layer));
  291. if (typeof font === "object") {
  292. element.css({
  293. font: textStyle,
  294. color: font.color
  295. });
  296. } else if (typeof font === "string") {
  297. element.addClass(font);
  298. }
  299. info = styleCache[text] = {
  300. width: element.outerWidth(true),
  301. height: element.outerHeight(true),
  302. element: element,
  303. positions: []
  304. };
  305. element.detach();
  306. }
  307. return info;
  308. };
  309. // Adds a text string to the canvas text overlay.
  310. //
  311. // The text isn't drawn immediately; it is marked as rendering, which will
  312. // result in its addition to the canvas on the next render pass.
  313. //
  314. // @param {string} layer A string of space-separated CSS classes uniquely
  315. // identifying the layer containing this text.
  316. // @param {number} x X coordinate at which to draw the text.
  317. // @param {number} y Y coordinate at which to draw the text.
  318. // @param {string} text Text string to draw.
  319. // @param {(string|object)=} font Either a string of space-separated CSS
  320. // classes or a font-spec object, defining the text's font and style.
  321. // @param {number=} angle Angle at which to rotate the text, in degrees.
  322. // Angle is currently unused, it will be implemented in the future.
  323. // @param {number=} width Maximum width of the text before it wraps.
  324. // @param {string=} halign Horizontal alignment of the text; either "left",
  325. // "center" or "right".
  326. // @param {string=} valign Vertical alignment of the text; either "top",
  327. // "middle" or "bottom".
  328. Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
  329. var info = this.getTextInfo(layer, text, font, angle, width),
  330. positions = info.positions;
  331. // Tweak the div's position to match the text's alignment
  332. if (halign == "center") {
  333. x -= info.width / 2;
  334. } else if (halign == "right") {
  335. x -= info.width;
  336. }
  337. if (valign == "middle") {
  338. y -= info.height / 2;
  339. } else if (valign == "bottom") {
  340. y -= info.height;
  341. }
  342. // Determine whether this text already exists at this position.
  343. // If so, mark it for inclusion in the next render pass.
  344. for (var i = 0, position; position = positions[i]; i++) {
  345. if (position.x == x && position.y == y) {
  346. position.active = true;
  347. return;
  348. }
  349. }
  350. // If the text doesn't exist at this position, create a new entry
  351. // For the very first position we'll re-use the original element,
  352. // while for subsequent ones we'll clone it.
  353. position = {
  354. active: true,
  355. rendered: false,
  356. element: positions.length ? info.element.clone() : info.element,
  357. x: x,
  358. y: y
  359. };
  360. positions.push(position);
  361. // Move the element to its final position within the container
  362. position.element.css({
  363. top: Math.round(y),
  364. left: Math.round(x),
  365. 'text-align': halign // In case the text wraps
  366. });
  367. };
  368. // Removes one or more text strings from the canvas text overlay.
  369. //
  370. // If no parameters are given, all text within the layer is removed.
  371. //
  372. // Note that the text is not immediately removed; it is simply marked as
  373. // inactive, which will result in its removal on the next render pass.
  374. // This avoids the performance penalty for 'clear and redraw' behavior,
  375. // where we potentially get rid of all text on a layer, but will likely
  376. // add back most or all of it later, as when redrawing axes, for example.
  377. //
  378. // @param {string} layer A string of space-separated CSS classes uniquely
  379. // identifying the layer containing this text.
  380. // @param {number=} x X coordinate of the text.
  381. // @param {number=} y Y coordinate of the text.
  382. // @param {string=} text Text string to remove.
  383. // @param {(string|object)=} font Either a string of space-separated CSS
  384. // classes or a font-spec object, defining the text's font and style.
  385. // @param {number=} angle Angle at which the text is rotated, in degrees.
  386. // Angle is currently unused, it will be implemented in the future.
  387. Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
  388. if (text == null) {
  389. var layerCache = this._textCache[layer];
  390. if (layerCache != null) {
  391. for (var styleKey in layerCache) {
  392. if (hasOwnProperty.call(layerCache, styleKey)) {
  393. var styleCache = layerCache[styleKey];
  394. for (var key in styleCache) {
  395. if (hasOwnProperty.call(styleCache, key)) {
  396. var positions = styleCache[key].positions;
  397. for (var i = 0, position; position = positions[i]; i++) {
  398. position.active = false;
  399. }
  400. }
  401. }
  402. }
  403. }
  404. }
  405. } else {
  406. var positions = this.getTextInfo(layer, text, font, angle).positions;
  407. for (var i = 0, position; position = positions[i]; i++) {
  408. if (position.x == x && position.y == y) {
  409. position.active = false;
  410. }
  411. }
  412. }
  413. };
  414. ///////////////////////////////////////////////////////////////////////////
  415. // The top-level container for the entire plot.
  416. function Plot(placeholder, data_, options_, plugins) {
  417. // data is on the form:
  418. // [ series1, series2 ... ]
  419. // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
  420. // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
  421. var series = [],
  422. options = {
  423. // the color theme used for graphs
  424. colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
  425. legend: {
  426. show: true,
  427. noColumns: 1, // number of colums in legend table
  428. labelFormatter: null, // fn: string -> string
  429. labelBoxBorderColor: "#ccc", // border color for the little label boxes
  430. container: null, // container (as jQuery object) to put legend in, null means default on top of graph
  431. position: "ne", // position of default legend container within plot
  432. margin: 5, // distance from grid edge to default legend container within plot
  433. backgroundColor: null, // null means auto-detect
  434. backgroundOpacity: 0.85, // set to 0 to avoid background
  435. sorted: null // default to no legend sorting
  436. },
  437. xaxis: {
  438. show: null, // null = auto-detect, true = always, false = never
  439. position: "bottom", // or "top"
  440. mode: null, // null or "time"
  441. font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
  442. color: null, // base color, labels, ticks
  443. tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
  444. transform: null, // null or f: number -> number to transform axis
  445. inverseTransform: null, // if transform is set, this should be the inverse function
  446. min: null, // min. value to show, null means set automatically
  447. max: null, // max. value to show, null means set automatically
  448. autoscaleMargin: null, // margin in % to add if auto-setting min/max
  449. ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
  450. tickFormatter: null, // fn: number -> string
  451. labelWidth: null, // size of tick labels in pixels
  452. labelHeight: null,
  453. reserveSpace: null, // whether to reserve space even if axis isn't shown
  454. tickLength: null, // size in pixels of ticks, or "full" for whole line
  455. alignTicksWithAxis: null, // axis number or null for no sync
  456. tickDecimals: null, // no. of decimals, null means auto
  457. tickSize: null, // number or [number, "unit"]
  458. minTickSize: null // number or [number, "unit"]
  459. },
  460. yaxis: {
  461. autoscaleMargin: 0.02,
  462. position: "left" // or "right"
  463. },
  464. xaxes: [],
  465. yaxes: [],
  466. series: {
  467. points: {
  468. show: false,
  469. radius: 3,
  470. lineWidth: 2, // in pixels
  471. fill: true,
  472. fillColor: "#ffffff",
  473. symbol: "circle" // or callback
  474. },
  475. lines: {
  476. // we don't put in show: false so we can see
  477. // whether lines were actively disabled
  478. lineWidth: 2, // in pixels
  479. fill: false,
  480. fillColor: null,
  481. steps: false
  482. // Omit 'zero', so we can later default its value to
  483. // match that of the 'fill' option.
  484. },
  485. bars: {
  486. show: false,
  487. lineWidth: 2, // in pixels
  488. barWidth: 1, // in units of the x axis
  489. fill: true,
  490. fillColor: null,
  491. align: "left", // "left", "right", or "center"
  492. horizontal: false,
  493. zero: true
  494. },
  495. shadowSize: 3,
  496. highlightColor: null
  497. },
  498. grid: {
  499. show: true,
  500. aboveData: false,
  501. color: "#545454", // primary color used for outline and labels
  502. backgroundColor: null, // null for transparent, else color
  503. borderColor: null, // set if different from the grid color
  504. tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
  505. margin: 0, // distance from the canvas edge to the grid
  506. labelMargin: 5, // in pixels
  507. axisMargin: 8, // in pixels
  508. borderWidth: 2, // in pixels
  509. minBorderMargin: null, // in pixels, null means taken from points radius
  510. markings: null, // array of ranges or fn: axes -> array of ranges
  511. markingsColor: "#f4f4f4",
  512. markingsLineWidth: 2,
  513. // interactive stuff
  514. clickable: false,
  515. hoverable: false,
  516. autoHighlight: true, // highlight in case mouse is near
  517. mouseActiveRadius: 10 // how far the mouse can be away to activate an item
  518. },
  519. interaction: {
  520. redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
  521. },
  522. hooks: {}
  523. },
  524. surface = null, // the canvas for the plot itself
  525. overlay = null, // canvas for interactive stuff on top of plot
  526. eventHolder = null, // jQuery object that events should be bound to
  527. ctx = null, octx = null,
  528. xaxes = [], yaxes = [],
  529. plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
  530. plotWidth = 0, plotHeight = 0,
  531. hooks = {
  532. processOptions: [],
  533. processRawData: [],
  534. processDatapoints: [],
  535. processOffset: [],
  536. drawBackground: [],
  537. drawSeries: [],
  538. draw: [],
  539. bindEvents: [],
  540. drawOverlay: [],
  541. shutdown: []
  542. },
  543. plot = this;
  544. // public functions
  545. plot.setData = setData;
  546. plot.setupGrid = setupGrid;
  547. plot.draw = draw;
  548. plot.getPlaceholder = function() { return placeholder; };
  549. plot.getCanvas = function() { return surface.element; };
  550. plot.getPlotOffset = function() { return plotOffset; };
  551. plot.width = function () { return plotWidth; };
  552. plot.height = function () { return plotHeight; };
  553. plot.offset = function () {
  554. var o = eventHolder.offset();
  555. o.left += plotOffset.left;
  556. o.top += plotOffset.top;
  557. return o;
  558. };
  559. plot.getData = function () { return series; };
  560. plot.getAxes = function () {
  561. var res = {}, i;
  562. $.each(xaxes.concat(yaxes), function (_, axis) {
  563. if (axis)
  564. res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
  565. });
  566. return res;
  567. };
  568. plot.getXAxes = function () { return xaxes; };
  569. plot.getYAxes = function () { return yaxes; };
  570. plot.c2p = canvasToAxisCoords;
  571. plot.p2c = axisToCanvasCoords;
  572. plot.getOptions = function () { return options; };
  573. plot.highlight = highlight;
  574. plot.unhighlight = unhighlight;
  575. plot.triggerRedrawOverlay = triggerRedrawOverlay;
  576. plot.pointOffset = function(point) {
  577. return {
  578. left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
  579. top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
  580. };
  581. };
  582. plot.shutdown = shutdown;
  583. plot.destroy = function () {
  584. shutdown();
  585. placeholder.removeData("plot").empty();
  586. series = [];
  587. options = null;
  588. surface = null;
  589. overlay = null;
  590. eventHolder = null;
  591. ctx = null;
  592. octx = null;
  593. xaxes = [];
  594. yaxes = [];
  595. hooks = null;
  596. highlights = [];
  597. plot = null;
  598. };
  599. plot.resize = function () {
  600. var width = placeholder.width(),
  601. height = placeholder.height();
  602. surface.resize(width, height);
  603. overlay.resize(width, height);
  604. };
  605. // public attributes
  606. plot.hooks = hooks;
  607. // initialize
  608. initPlugins(plot);
  609. parseOptions(options_);
  610. setupCanvases();
  611. setData(data_);
  612. setupGrid();
  613. draw();
  614. bindEvents();
  615. function executeHooks(hook, args) {
  616. args = [plot].concat(args);
  617. for (var i = 0; i < hook.length; ++i)
  618. hook[i].apply(this, args);
  619. }
  620. function initPlugins() {
  621. // References to key classes, allowing plugins to modify them
  622. var classes = {
  623. Canvas: Canvas
  624. };
  625. for (var i = 0; i < plugins.length; ++i) {
  626. var p = plugins[i];
  627. p.init(plot, classes);
  628. if (p.options)
  629. $.extend(true, options, p.options);
  630. }
  631. }
  632. function parseOptions(opts) {
  633. $.extend(true, options, opts);
  634. // $.extend merges arrays, rather than replacing them. When less
  635. // colors are provided than the size of the default palette, we
  636. // end up with those colors plus the remaining defaults, which is
  637. // not expected behavior; avoid it by replacing them here.
  638. if (opts && opts.colors) {
  639. options.colors = opts.colors;
  640. }
  641. if (options.xaxis.color == null)
  642. options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  643. if (options.yaxis.color == null)
  644. options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  645. if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
  646. options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
  647. if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
  648. options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
  649. if (options.grid.borderColor == null)
  650. options.grid.borderColor = options.grid.color;
  651. if (options.grid.tickColor == null)
  652. options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
  653. // Fill in defaults for axis options, including any unspecified
  654. // font-spec fields, if a font-spec was provided.
  655. // If no x/y axis options were provided, create one of each anyway,
  656. // since the rest of the code assumes that they exist.
  657. var i, axisOptions, axisCount,
  658. fontSize = placeholder.css("font-size"),
  659. fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
  660. fontDefaults = {
  661. style: placeholder.css("font-style"),
  662. size: Math.round(0.8 * fontSizeDefault),
  663. variant: placeholder.css("font-variant"),
  664. weight: placeholder.css("font-weight"),
  665. family: placeholder.css("font-family")
  666. };
  667. axisCount = options.xaxes.length || 1;
  668. for (i = 0; i < axisCount; ++i) {
  669. axisOptions = options.xaxes[i];
  670. if (axisOptions && !axisOptions.tickColor) {
  671. axisOptions.tickColor = axisOptions.color;
  672. }
  673. axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
  674. options.xaxes[i] = axisOptions;
  675. if (axisOptions.font) {
  676. axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
  677. if (!axisOptions.font.color) {
  678. axisOptions.font.color = axisOptions.color;
  679. }
  680. if (!axisOptions.font.lineHeight) {
  681. axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
  682. }
  683. }
  684. }
  685. axisCount = options.yaxes.length || 1;
  686. for (i = 0; i < axisCount; ++i) {
  687. axisOptions = options.yaxes[i];
  688. if (axisOptions && !axisOptions.tickColor) {
  689. axisOptions.tickColor = axisOptions.color;
  690. }
  691. axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
  692. options.yaxes[i] = axisOptions;
  693. if (axisOptions.font) {
  694. axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
  695. if (!axisOptions.font.color) {
  696. axisOptions.font.color = axisOptions.color;
  697. }
  698. if (!axisOptions.font.lineHeight) {
  699. axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
  700. }
  701. }
  702. }
  703. // backwards compatibility, to be removed in future
  704. if (options.xaxis.noTicks && options.xaxis.ticks == null)
  705. options.xaxis.ticks = options.xaxis.noTicks;
  706. if (options.yaxis.noTicks && options.yaxis.ticks == null)
  707. options.yaxis.ticks = options.yaxis.noTicks;
  708. if (options.x2axis) {
  709. options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
  710. options.xaxes[1].position = "top";
  711. // Override the inherit to allow the axis to auto-scale
  712. if (options.x2axis.min == null) {
  713. options.xaxes[1].min = null;
  714. }
  715. if (options.x2axis.max == null) {
  716. options.xaxes[1].max = null;
  717. }
  718. }
  719. if (options.y2axis) {
  720. options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
  721. options.yaxes[1].position = "right";
  722. // Override the inherit to allow the axis to auto-scale
  723. if (options.y2axis.min == null) {
  724. options.yaxes[1].min = null;
  725. }
  726. if (options.y2axis.max == null) {
  727. options.yaxes[1].max = null;
  728. }
  729. }
  730. if (options.grid.coloredAreas)
  731. options.grid.markings = options.grid.coloredAreas;
  732. if (options.grid.coloredAreasColor)
  733. options.grid.markingsColor = options.grid.coloredAreasColor;
  734. if (options.lines)
  735. $.extend(true, options.series.lines, options.lines);
  736. if (options.points)
  737. $.extend(true, options.series.points, options.points);
  738. if (options.bars)
  739. $.extend(true, options.series.bars, options.bars);
  740. if (options.shadowSize != null)
  741. options.series.shadowSize = options.shadowSize;
  742. if (options.highlightColor != null)
  743. options.series.highlightColor = options.highlightColor;
  744. // save options on axes for future reference
  745. for (i = 0; i < options.xaxes.length; ++i)
  746. getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
  747. for (i = 0; i < options.yaxes.length; ++i)
  748. getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
  749. // add hooks from options
  750. for (var n in hooks)
  751. if (options.hooks[n] && options.hooks[n].length)
  752. hooks[n] = hooks[n].concat(options.hooks[n]);
  753. executeHooks(hooks.processOptions, [options]);
  754. }
  755. function setData(d) {
  756. series = parseData(d);
  757. fillInSeriesOptions();
  758. processData();
  759. }
  760. function parseData(d) {
  761. var res = [];
  762. for (var i = 0; i < d.length; ++i) {
  763. var s = $.extend(true, {}, options.series);
  764. if (d[i].data != null) {
  765. s.data = d[i].data; // move the data instead of deep-copy
  766. delete d[i].data;
  767. $.extend(true, s, d[i]);
  768. d[i].data = s.data;
  769. }
  770. else
  771. s.data = d[i];
  772. res.push(s);
  773. }
  774. return res;
  775. }
  776. function axisNumber(obj, coord) {
  777. var a = obj[coord + "axis"];
  778. if (typeof a == "object") // if we got a real axis, extract number
  779. a = a.n;
  780. if (typeof a != "number")
  781. a = 1; // default to first axis
  782. return a;
  783. }
  784. function allAxes() {
  785. // return flat array without annoying null entries
  786. return $.grep(xaxes.concat(yaxes), function (a) { return a; });
  787. }
  788. function canvasToAxisCoords(pos) {
  789. // return an object with x/y corresponding to all used axes
  790. var res = {}, i, axis;
  791. for (i = 0; i < xaxes.length; ++i) {
  792. axis = xaxes[i];
  793. if (axis && axis.used)
  794. res["x" + axis.n] = axis.c2p(pos.left);
  795. }
  796. for (i = 0; i < yaxes.length; ++i) {
  797. axis = yaxes[i];
  798. if (axis && axis.used)
  799. res["y" + axis.n] = axis.c2p(pos.top);
  800. }
  801. if (res.x1 !== undefined)
  802. res.x = res.x1;
  803. if (res.y1 !== undefined)
  804. res.y = res.y1;
  805. return res;
  806. }
  807. function axisToCanvasCoords(pos) {
  808. // get canvas coords from the first pair of x/y found in pos
  809. var res = {}, i, axis, key;
  810. for (i = 0; i < xaxes.length; ++i) {
  811. axis = xaxes[i];
  812. if (axis && axis.used) {
  813. key = "x" + axis.n;
  814. if (pos[key] == null && axis.n == 1)
  815. key = "x";
  816. if (pos[key] != null) {
  817. res.left = axis.p2c(pos[key]);
  818. break;
  819. }
  820. }
  821. }
  822. for (i = 0; i < yaxes.length; ++i) {
  823. axis = yaxes[i];
  824. if (axis && axis.used) {
  825. key = "y" + axis.n;
  826. if (pos[key] == null && axis.n == 1)
  827. key = "y";
  828. if (pos[key] != null) {
  829. res.top = axis.p2c(pos[key]);
  830. break;
  831. }
  832. }
  833. }
  834. return res;
  835. }
  836. function getOrCreateAxis(axes, number) {
  837. if (!axes[number - 1])
  838. axes[number - 1] = {
  839. n: number, // save the number for future reference
  840. direction: axes == xaxes ? "x" : "y",
  841. options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
  842. };
  843. return axes[number - 1];
  844. }
  845. function fillInSeriesOptions() {
  846. var neededColors = series.length, maxIndex = -1, i;
  847. // Subtract the number of series that already have fixed colors or
  848. // color indexes from the number that we still need to generate.
  849. for (i = 0; i < series.length; ++i) {
  850. var sc = series[i].color;
  851. if (sc != null) {
  852. neededColors--;
  853. if (typeof sc == "number" && sc > maxIndex) {
  854. maxIndex = sc;
  855. }
  856. }
  857. }
  858. // If any of the series have fixed color indexes, then we need to
  859. // generate at least as many colors as the highest index.
  860. if (neededColors <= maxIndex) {
  861. neededColors = maxIndex + 1;
  862. }
  863. // Generate all the colors, using first the option colors and then
  864. // variations on those colors once they're exhausted.
  865. var c, colors = [], colorPool = options.colors,
  866. colorPoolSize = colorPool.length, variation = 0;
  867. for (i = 0; i < neededColors; i++) {
  868. c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
  869. // Each time we exhaust the colors in the pool we adjust
  870. // a scaling factor used to produce more variations on
  871. // those colors. The factor alternates negative/positive
  872. // to produce lighter/darker colors.
  873. // Reset the variation after every few cycles, or else
  874. // it will end up producing only white or black colors.
  875. if (i % colorPoolSize == 0 && i) {
  876. if (variation >= 0) {
  877. if (variation < 0.5) {
  878. variation = -variation - 0.2;
  879. } else variation = 0;
  880. } else variation = -variation;
  881. }
  882. colors[i] = c.scale('rgb', 1 + variation);
  883. }
  884. // Finalize the series options, filling in their colors
  885. var colori = 0, s;
  886. for (i = 0; i < series.length; ++i) {
  887. s = series[i];
  888. // assign colors
  889. if (s.color == null) {
  890. s.color = colors[colori].toString();
  891. ++colori;
  892. }
  893. else if (typeof s.color == "number")
  894. s.color = colors[s.color].toString();
  895. // turn on lines automatically in case nothing is set
  896. if (s.lines.show == null) {
  897. var v, show = true;
  898. for (v in s)
  899. if (s[v] && s[v].show) {
  900. show = false;
  901. break;
  902. }
  903. if (show)
  904. s.lines.show = true;
  905. }
  906. // If nothing was provided for lines.zero, default it to match
  907. // lines.fill, since areas by default should extend to zero.
  908. if (s.lines.zero == null) {
  909. s.lines.zero = !!s.lines.fill;
  910. }
  911. // setup axes
  912. s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
  913. s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
  914. }
  915. }
  916. function processData() {
  917. var topSentry = Number.POSITIVE_INFINITY,
  918. bottomSentry = Number.NEGATIVE_INFINITY,
  919. fakeInfinity = Number.MAX_VALUE,
  920. i, j, k, m, length,
  921. s, points, ps, x, y, axis, val, f, p,
  922. data, format;
  923. function updateAxis(axis, min, max) {
  924. if (min < axis.datamin && min != -fakeInfinity)
  925. axis.datamin = min;
  926. if (max > axis.datamax && max != fakeInfinity)
  927. axis.datamax = max;
  928. }
  929. $.each(allAxes(), function (_, axis) {
  930. // init axis
  931. axis.datamin = topSentry;
  932. axis.datamax = bottomSentry;
  933. axis.used = false;
  934. });
  935. for (i = 0; i < series.length; ++i) {
  936. s = series[i];
  937. s.datapoints = { points: [] };
  938. executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
  939. }
  940. // first pass: clean and copy data
  941. for (i = 0; i < series.length; ++i) {
  942. s = series[i];
  943. data = s.data;
  944. format = s.datapoints.format;
  945. if (!format) {
  946. format = [];
  947. // find out how to copy
  948. format.push({ x: true, number: true, required: true });
  949. format.push({ y: true, number: true, required: true });
  950. if (s.bars.show || (s.lines.show && s.lines.fill)) {
  951. var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
  952. format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
  953. if (s.bars.horizontal) {
  954. delete format[format.length - 1].y;
  955. format[format.length - 1].x = true;
  956. }
  957. }
  958. s.datapoints.format = format;
  959. }
  960. if (s.datapoints.pointsize != null)
  961. continue; // already filled in
  962. s.datapoints.pointsize = format.length;
  963. ps = s.datapoints.pointsize;
  964. points = s.datapoints.points;
  965. var insertSteps = s.lines.show && s.lines.steps;
  966. s.xaxis.used = s.yaxis.used = true;
  967. for (j = k = 0; j < data.length; ++j, k += ps) {
  968. p = data[j];
  969. var nullify = p == null;
  970. if (!nullify) {
  971. for (m = 0; m < ps; ++m) {
  972. val = p[m];
  973. f = format[m];
  974. if (f) {
  975. if (f.number && val != null) {
  976. val = +val; // convert to number
  977. if (isNaN(val))
  978. val = null;
  979. else if (val == Infinity)
  980. val = fakeInfinity;
  981. else if (val == -Infinity)
  982. val = -fakeInfinity;
  983. }
  984. if (val == null) {
  985. if (f.required)
  986. nullify = true;
  987. if (f.defaultValue != null)
  988. val = f.defaultValue;
  989. }
  990. }
  991. points[k + m] = val;
  992. }
  993. }
  994. if (nullify) {
  995. for (m = 0; m < ps; ++m) {
  996. val = points[k + m];
  997. if (val != null) {
  998. f = format[m];
  999. // extract min/max info
  1000. if (f.autoscale !== false) {
  1001. if (f.x) {
  1002. updateAxis(s.xaxis, val, val);
  1003. }
  1004. if (f.y) {
  1005. updateAxis(s.yaxis, val, val);
  1006. }
  1007. }
  1008. }
  1009. points[k + m] = null;
  1010. }
  1011. }
  1012. else {
  1013. // a little bit of line specific stuff that
  1014. // perhaps shouldn't be here, but lacking
  1015. // better means...
  1016. if (insertSteps && k > 0
  1017. && points[k - ps] != null
  1018. && points[k - ps] != points[k]
  1019. && points[k - ps + 1] != points[k + 1]) {
  1020. // copy the point to make room for a middle point
  1021. for (m = 0; m < ps; ++m)
  1022. points[k + ps + m] = points[k + m];
  1023. // middle point has same y
  1024. points[k + 1] = points[k - ps + 1];
  1025. // we've added a point, better reflect that
  1026. k += ps;
  1027. }
  1028. }
  1029. }
  1030. }
  1031. // give the hooks a chance to run
  1032. for (i = 0; i < series.length; ++i) {
  1033. s = series[i];
  1034. executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
  1035. }
  1036. // second pass: find datamax/datamin for auto-scaling
  1037. for (i = 0; i < series.length; ++i) {
  1038. s = series[i];
  1039. points = s.datapoints.points;
  1040. ps = s.datapoints.pointsize;
  1041. format = s.datapoints.format;
  1042. var xmin = topSentry, ymin = topSentry,
  1043. xmax = bottomSentry, ymax = bottomSentry;
  1044. for (j = 0; j < points.length; j += ps) {
  1045. if (points[j] == null)
  1046. continue;
  1047. for (m = 0; m < ps; ++m) {
  1048. val = points[j + m];
  1049. f = format[m];
  1050. if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
  1051. continue;
  1052. if (f.x) {
  1053. if (val < xmin)
  1054. xmin = val;
  1055. if (val > xmax)
  1056. xmax = val;
  1057. }
  1058. if (f.y) {
  1059. if (val < ymin)
  1060. ymin = val;
  1061. if (val > ymax)
  1062. ymax = val;
  1063. }
  1064. }
  1065. }
  1066. if (s.bars.show) {
  1067. // make sure we got room for the bar on the dancing floor
  1068. var delta;
  1069. switch (s.bars.align) {
  1070. case "left":
  1071. delta = 0;
  1072. break;
  1073. case "right":
  1074. delta = -s.bars.barWidth;
  1075. break;
  1076. default:
  1077. delta = -s.bars.barWidth / 2;
  1078. }
  1079. if (s.bars.horizontal) {
  1080. ymin += delta;
  1081. ymax += delta + s.bars.barWidth;
  1082. }
  1083. else {
  1084. xmin += delta;
  1085. xmax += delta + s.bars.barWidth;
  1086. }
  1087. }
  1088. updateAxis(s.xaxis, xmin, xmax);
  1089. updateAxis(s.yaxis, ymin, ymax);
  1090. }
  1091. $.each(allAxes(), function (_, axis) {
  1092. if (axis.datamin == topSentry)
  1093. axis.datamin = null;
  1094. if (axis.datamax == bottomSentry)
  1095. axis.datamax = null;
  1096. });
  1097. }
  1098. function setupCanvases() {
  1099. // Make sure the placeholder is clear of everything except canvases
  1100. // from a previous plot in this container that we'll try to re-use.
  1101. placeholder.css("padding", 0) // padding messes up the positioning
  1102. .children().filter(function(){
  1103. return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
  1104. }).remove();
  1105. if (placeholder.css("position") == 'static')
  1106. placeholder.css("position", "relative"); // for positioning labels and overlay
  1107. surface = new Canvas("flot-base", placeholder);
  1108. overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
  1109. ctx = surface.context;
  1110. octx = overlay.context;
  1111. // define which element we're listening for events on
  1112. eventHolder = $(overlay.element).unbind();
  1113. // If we're re-using a plot object, shut down the old one
  1114. var existing = placeholder.data("plot");
  1115. if (existing) {
  1116. existing.shutdown();
  1117. overlay.clear();
  1118. }
  1119. // save in case we get replotted
  1120. placeholder.data("plot", plot);
  1121. }
  1122. function bindEvents() {
  1123. // bind events
  1124. if (options.grid.hoverable) {
  1125. eventHolder.mousemove(onMouseMove);
  1126. // Use bind, rather than .mouseleave, because we officially
  1127. // still support jQuery 1.2.6, which doesn't define a shortcut
  1128. // for mouseenter or mouseleave. This was a bug/oversight that
  1129. // was fixed somewhere around 1.3.x. We can return to using
  1130. // .mouseleave when we drop support for 1.2.6.
  1131. eventHolder.bind("mouseleave", onMouseLeave);
  1132. }
  1133. if (options.grid.clickable)
  1134. eventHolder.click(onClick);
  1135. executeHooks(hooks.bindEvents, [eventHolder]);
  1136. }
  1137. function shutdown() {
  1138. if (redrawTimeout)
  1139. clearTimeout(redrawTimeout);
  1140. eventHolder.unbind("mousemove", onMouseMove);
  1141. eventHolder.unbind("mouseleave", onMouseLeave);
  1142. eventHolder.unbind("click", onClick);
  1143. executeHooks(hooks.shutdown, [eventHolder]);
  1144. }
  1145. function setTransformationHelpers(axis) {
  1146. // set helper functions on the axis, assumes plot area
  1147. // has been computed already
  1148. function identity(x) { return x; }
  1149. var s, m, t = axis.options.transform || identity,
  1150. it = axis.options.inverseTransform;
  1151. // precompute how much the axis is scaling a point
  1152. // in canvas space
  1153. if (axis.direction == "x") {
  1154. s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
  1155. m = Math.min(t(axis.max), t(axis.min));
  1156. }
  1157. else {
  1158. s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
  1159. s = -s;
  1160. m = Math.max(t(axis.max), t(axis.min));
  1161. }
  1162. // data point to canvas coordinate
  1163. if (t == identity) // slight optimization
  1164. axis.p2c = function (p) { return (p - m) * s; };
  1165. else
  1166. axis.p2c = function (p) { return (t(p) - m) * s; };
  1167. // canvas coordinate to data point
  1168. if (!it)
  1169. axis.c2p = function (c) { return m + c / s; };
  1170. else
  1171. axis.c2p = function (c) { return it(m + c / s); };
  1172. }
  1173. function measureTickLabels(axis) {
  1174. var opts = axis.options,
  1175. ticks = axis.ticks || [],
  1176. labelWidth = opts.labelWidth || 0,
  1177. labelHeight = opts.labelHeight || 0,
  1178. maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
  1179. legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
  1180. layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
  1181. font = opts.font || "flot-tick-label tickLabel";
  1182. for (var i = 0; i < ticks.length; ++i) {
  1183. var t = ticks[i];
  1184. if (!t.label)
  1185. continue;
  1186. var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
  1187. labelWidth = Math.max(labelWidth, info.width);
  1188. labelHeight = Math.max(labelHeight, info.height);
  1189. }
  1190. axis.labelWidth = opts.labelWidth || labelWidth;
  1191. axis.labelHeight = opts.labelHeight || labelHeight;
  1192. }
  1193. function allocateAxisBoxFirstPhase(axis) {
  1194. // find the bounding box of the axis by looking at label
  1195. // widths/heights and ticks, make room by diminishing the
  1196. // plotOffset; this first phase only looks at one
  1197. // dimension per axis, the other dimension depends on the
  1198. // other axes so will have to wait
  1199. var lw = axis.labelWidth,
  1200. lh = axis.labelHeight,
  1201. pos = axis.options.position,
  1202. isXAxis = axis.direction === "x",
  1203. tickLength = axis.options.tickLength,
  1204. axisMargin = options.grid.axisMargin,
  1205. padding = options.grid.labelMargin,
  1206. innermost = true,
  1207. outermost = true,
  1208. first = true,
  1209. found = false;
  1210. // Determine the axis's position in its direction and on its side
  1211. $.each(isXAxis ? xaxes : yaxes, function(i, a) {
  1212. if (a && (a.show || a.reserveSpace)) {
  1213. if (a === axis) {
  1214. found = true;
  1215. } else if (a.options.position === pos) {
  1216. if (found) {
  1217. outermost = false;
  1218. } else {
  1219. innermost = false;
  1220. }
  1221. }
  1222. if (!found) {
  1223. first = false;
  1224. }
  1225. }
  1226. });
  1227. // The outermost axis on each side has no margin
  1228. if (outermost) {
  1229. axisMargin = 0;
  1230. }
  1231. // The ticks for the first axis in each direction stretch across
  1232. if (tickLength == null) {
  1233. tickLength = first ? "full" : 5;
  1234. }
  1235. if (!isNaN(+tickLength))
  1236. padding += +tickLength;
  1237. if (isXAxis) {
  1238. lh += padding;
  1239. if (pos == "bottom") {
  1240. plotOffset.bottom += lh + axisMargin;
  1241. axis.box = { top: surface.height - plotOffset.bottom, height: lh };
  1242. }
  1243. else {
  1244. axis.box = { top: plotOffset.top + axisMargin, height: lh };
  1245. plotOffset.top += lh + axisMargin;
  1246. }
  1247. }
  1248. else {
  1249. lw += padding;
  1250. if (pos == "left") {
  1251. axis.box = { left: plotOffset.left + axisMargin, width: lw };
  1252. plotOffset.left += lw + axisMargin;
  1253. }
  1254. else {
  1255. plotOffset.right += lw + axisMargin;
  1256. axis.box = { left: surface.width - plotOffset.right, width: lw };
  1257. }
  1258. }
  1259. // save for future reference
  1260. axis.position = pos;
  1261. axis.tickLength = tickLength;
  1262. axis.box.padding = padding;
  1263. axis.innermost = innermost;
  1264. }
  1265. function allocateAxisBoxSecondPhase(axis) {
  1266. // now that all axis boxes have been placed in one
  1267. // dimension, we can set the remaining dimension coordinates
  1268. if (axis.direction == "x") {
  1269. axis.box.left = plotOffset.left - axis.labelWidth / 2;
  1270. axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
  1271. }
  1272. else {
  1273. axis.box.top = plotOffset.top - axis.labelHeight / 2;
  1274. axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
  1275. }
  1276. }
  1277. function adjustLayoutForThingsStickingOut() {
  1278. // possibly adjust plot offset to ensure everything stays
  1279. // inside the canvas and isn't clipped off
  1280. var minMargin = options.grid.minBorderMargin,
  1281. axis, i;
  1282. // check stuff from the plot (FIXME: this should just read
  1283. // a value from the series, otherwise it's impossible to
  1284. // customize)
  1285. if (minMargin == null) {
  1286. minMargin = 0;
  1287. for (i = 0; i < series.length; ++i)
  1288. minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
  1289. }
  1290. var margins = {
  1291. left: minMargin,
  1292. right: minMargin,
  1293. top: minMargin,
  1294. bottom: minMargin
  1295. };
  1296. // check axis labels, note we don't check the actual
  1297. // labels but instead use the overall width/height to not
  1298. // jump as much around with replots
  1299. $.each(allAxes(), function (_, axis) {
  1300. if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
  1301. if (axis.direction === "x") {
  1302. margins.left = Math.max(margins.left, axis.labelWidth / 2);
  1303. margins.right = Math.max(margins.right, axis.labelWidth / 2);
  1304. } else {
  1305. margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
  1306. margins.top = Math.max(margins.top, axis.labelHeight / 2);
  1307. }
  1308. }
  1309. });
  1310. plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
  1311. plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
  1312. plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
  1313. plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
  1314. }
  1315. function setupGrid() {
  1316. var i, axes = allAxes(), showGrid = options.grid.show;
  1317. // Initialize the plot's offset from the edge of the canvas
  1318. for (var a in plotOffset) {
  1319. var margin = options.grid.margin || 0;
  1320. plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
  1321. }
  1322. executeHooks(hooks.processOffset, [plotOffset]);
  1323. // If the grid is visible, add its border width to the offset
  1324. for (var a in plotOffset) {
  1325. if(typeof(options.grid.borderWidth) == "object") {
  1326. plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
  1327. }
  1328. else {
  1329. plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
  1330. }
  1331. }
  1332. $.each(axes, function (_, axis) {
  1333. var axisOpts = axis.options;
  1334. axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
  1335. axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
  1336. setRange(axis);
  1337. });
  1338. if (showGrid) {
  1339. var allocatedAxes = $.grep(axes, function (axis) {
  1340. return axis.show || axis.reserveSpace;
  1341. });
  1342. $.each(allocatedAxes, function (_, axis) {
  1343. // make the ticks
  1344. setupTickGeneration(axis);
  1345. setTicks(axis);
  1346. snapRangeToTicks(axis, axis.ticks);
  1347. // find labelWidth/Height for axis
  1348. measureTickLabels(axis);
  1349. });
  1350. // with all dimensions calculated, we can compute the
  1351. // axis bounding boxes, start from the outside
  1352. // (reverse order)
  1353. for (i = allocatedAxes.length - 1; i >= 0; --i)
  1354. allocateAxisBoxFirstPhase(allocatedAxes[i]);
  1355. // make sure we've got enough space for things that
  1356. // might stick out
  1357. adjustLayoutForThingsStickingOut();
  1358. $.each(allocatedAxes, function (_, axis) {
  1359. allocateAxisBoxSecondPhase(axis);
  1360. });
  1361. }
  1362. plotWidth = surface.width - plotOffset.left - plotOffset.right;
  1363. plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
  1364. // now we got the proper plot dimensions, we can compute the scaling
  1365. $.each(axes, function (_, axis) {
  1366. setTransformationHelpers(axis);
  1367. });
  1368. if (showGrid) {
  1369. drawAxisLabels();
  1370. }
  1371. insertLegend();
  1372. }
  1373. function setRange(axis) {
  1374. var opts = axis.options,
  1375. min = +(opts.min != null ? opts.min : axis.datamin),
  1376. max = +(opts.max != null ? opts.max : axis.datamax),
  1377. delta = max - min;
  1378. if (delta == 0.0) {
  1379. // degenerate case
  1380. var widen = max == 0 ? 1 : 0.01;
  1381. if (opts.min == null)
  1382. min -= widen;
  1383. // always widen max if we couldn't widen min to ensure we
  1384. // don't fall into min == max which doesn't work
  1385. if (opts.max == null || opts.min != null)
  1386. max += widen;
  1387. }
  1388. else {
  1389. // consider autoscaling
  1390. var margin = opts.autoscaleMargin;
  1391. if (margin != null) {
  1392. if (opts.min == null) {
  1393. min -= delta * margin;
  1394. // make sure we don't go below zero if all values
  1395. // are positive
  1396. if (min < 0 && axis.datamin != null && axis.datamin >= 0)
  1397. min = 0;
  1398. }
  1399. if (opts.max == null) {
  1400. max += delta * margin;
  1401. if (max > 0 && axis.datamax != null && axis.datamax <= 0)
  1402. max = 0;
  1403. }
  1404. }
  1405. }
  1406. axis.min = min;
  1407. axis.max = max;
  1408. }
  1409. function setupTickGeneration(axis) {
  1410. var opts = axis.options;
  1411. // estimate number of ticks
  1412. var noTicks;
  1413. if (typeof opts.ticks == "number" && opts.ticks > 0)
  1414. noTicks = opts.ticks;
  1415. else
  1416. // heuristic based on the model a*sqrt(x) fitted to
  1417. // some data points that seemed reasonable
  1418. noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
  1419. var delta = (axis.max - axis.min) / noTicks,
  1420. dec = -Math.floor(Math.log(delta) / Math.LN10),
  1421. maxDec = opts.tickDecimals;
  1422. if (maxDec != null && dec > maxDec) {
  1423. dec = maxDec;
  1424. }
  1425. var magn = Math.pow(10, -dec),
  1426. norm = delta / magn, // norm is between 1.0 and 10.0
  1427. size;
  1428. if (norm < 1.5) {
  1429. size = 1;
  1430. } else if (norm < 3) {
  1431. size = 2;
  1432. // special case for 2.5, requires an extra decimal
  1433. if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
  1434. size = 2.5;
  1435. ++dec;
  1436. }
  1437. } else if (norm < 7.5) {
  1438. size = 5;
  1439. } else {
  1440. size = 10;
  1441. }
  1442. size *= magn;
  1443. if (opts.minTickSize != null && size < opts.minTickSize) {
  1444. size = opts.minTickSize;
  1445. }
  1446. axis.delta = delta;
  1447. axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
  1448. axis.tickSize = opts.tickSize || size;
  1449. // Time mode was moved to a plug-in in 0.8, and since so many people use it
  1450. // we'll add an especially friendly reminder to make sure they included it.
  1451. if (opts.mode == "time" && !axis.tickGenerator) {
  1452. throw new Error("Time mode requires the flot.time plugin.");
  1453. }
  1454. // Flot supports base-10 axes; any other mode else is handled by a plug-in,
  1455. // like flot.time.js.
  1456. if (!axis.tickGenerator) {
  1457. axis.tickGenerator = function (axis) {
  1458. var ticks = [],
  1459. start = floorInBase(axis.min, axis.tickSize),
  1460. i = 0,
  1461. v = Number.NaN,
  1462. prev;
  1463. do {
  1464. prev = v;
  1465. v = start + i * axis.tickSize;
  1466. ticks.push(v);
  1467. ++i;
  1468. } while (v < axis.max && v != prev);
  1469. return ticks;
  1470. };
  1471. axis.tickFormatter = function (value, axis) {
  1472. var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
  1473. var formatted = "" + Math.round(value * factor) / factor;
  1474. // If tickDecimals was specified, ensure that we have exactly that
  1475. // much precision; otherwise default to the value's own precision.
  1476. if (axis.tickDecimals != null) {
  1477. var decimal = formatted.indexOf(".");
  1478. var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
  1479. if (precision < axis.tickDecimals) {
  1480. return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
  1481. }
  1482. }
  1483. return formatted;
  1484. };
  1485. }
  1486. if ($.isFunction(opts.tickFormatter))
  1487. axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
  1488. if (opts.alignTicksWithAxis != null) {
  1489. var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
  1490. if (otherAxis && otherAxis.used && otherAxis != axis) {
  1491. // consider snapping min/max to outermost nice ticks
  1492. var niceTicks = axis.tickGenerator(axis);
  1493. if (niceTicks.length > 0) {
  1494. if (opts.min == null)
  1495. axis.min = Math.min(axis.min, niceTicks[0]);
  1496. if (opts.max == null && niceTicks.length > 1)
  1497. axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
  1498. }
  1499. axis.tickGenerator = function (axis) {
  1500. // copy ticks, scaled to this axis
  1501. var ticks = [], v, i;
  1502. for (i = 0; i < otherAxis.ticks.length; ++i) {
  1503. v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
  1504. v = axis.min + v * (axis.max - axis.min);
  1505. ticks.push(v);
  1506. }
  1507. return ticks;
  1508. };
  1509. // we might need an extra decimal since forced
  1510. // ticks don't necessarily fit naturally
  1511. if (!axis.mode && opts.tickDecimals == null) {
  1512. var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
  1513. ts = axis.tickGenerator(axis);
  1514. // only proceed if the tick interval rounded
  1515. // with an extra decimal doesn't give us a
  1516. // zero at end
  1517. if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
  1518. axis.tickDecimals = extraDec;
  1519. }
  1520. }
  1521. }
  1522. }
  1523. function setTicks(axis) {
  1524. var oticks = axis.options.ticks, ticks = [];
  1525. if (oticks == null || (typeof oticks == "number" && oticks > 0))
  1526. ticks = axis.tickGenerator(axis);
  1527. else if (oticks) {
  1528. if ($.isFunction(oticks))
  1529. // generate the ticks
  1530. ticks = oticks(axis);
  1531. else
  1532. ticks = oticks;
  1533. }
  1534. // clean up/labelify the supplied ticks, copy them over
  1535. var i, v;
  1536. axis.ticks = [];
  1537. for (i = 0; i < ticks.length; ++i) {
  1538. var label = null;
  1539. var t = ticks[i];
  1540. if (typeof t == "object") {
  1541. v = +t[0];
  1542. if (t.length > 1)
  1543. label = t[1];
  1544. }
  1545. else
  1546. v = +t;
  1547. if (label == null)
  1548. label = axis.tickFormatter(v, axis);
  1549. if (!isNaN(v))
  1550. axis.ticks.push({ v: v, label: label });
  1551. }
  1552. }
  1553. function snapRangeToTicks(axis, ticks) {
  1554. if (axis.options.autoscaleMargin && ticks.length > 0) {
  1555. // snap to ticks
  1556. if (axis.options.min == null)
  1557. axis.min = Math.min(axis.min, ticks[0].v);
  1558. if (axis.options.max == null && ticks.length > 1)
  1559. axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
  1560. }
  1561. }
  1562. function draw() {
  1563. surface.clear();
  1564. executeHooks(hooks.drawBackground, [ctx]);
  1565. var grid = options.grid;
  1566. // draw background, if any
  1567. if (grid.show && grid.backgroundColor)
  1568. drawBackground();
  1569. if (grid.show && !grid.aboveData) {
  1570. drawGrid();
  1571. }
  1572. for (var i = 0; i < series.length; ++i) {
  1573. executeHooks(hooks.drawSeries, [ctx, series[i]]);
  1574. drawSeries(series[i]);
  1575. }
  1576. executeHooks(hooks.draw, [ctx]);
  1577. if (grid.show && grid.aboveData) {
  1578. drawGrid();
  1579. }
  1580. surface.render();
  1581. // A draw implies that either the axes or data have changed, so we
  1582. // should probably update the overlay highlights as well.
  1583. triggerRedrawOverlay();
  1584. }
  1585. function extractRange(ranges, coord) {
  1586. var axis, from, to, key, axes = allAxes();
  1587. for (var i = 0; i < axes.length; ++i) {
  1588. axis = axes[i];
  1589. if (axis.direction == coord) {
  1590. key = coord + axis.n + "axis";
  1591. if (!ranges[key] && axis.n == 1)
  1592. key = coord + "axis"; // support x1axis as xaxis
  1593. if (ranges[key]) {
  1594. from = ranges[key].from;
  1595. to = ranges[key].to;
  1596. break;
  1597. }
  1598. }
  1599. }
  1600. // backwards-compat stuff - to be removed in future
  1601. if (!ranges[key]) {
  1602. axis = coord == "x" ? xaxes[0] : yaxes[0];
  1603. from = ranges[coord + "1"];
  1604. to = ranges[coord + "2"];
  1605. }
  1606. // auto-reverse as an added bonus
  1607. if (from != null && to != null && from > to) {
  1608. var tmp = from;
  1609. from = to;
  1610. to = tmp;
  1611. }
  1612. return { from: from, to: to, axis: axis };
  1613. }
  1614. function drawBackground() {
  1615. ctx.save();
  1616. ctx.translate(plotOffset.left, plotOffset.top);
  1617. ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
  1618. ctx.fillRect(0, 0, plotWidth, plotHeight);
  1619. ctx.restore();
  1620. }
  1621. function drawGrid() {
  1622. var i, axes, bw, bc;
  1623. ctx.save();
  1624. ctx.translate(plotOffset.left, plotOffset.top);
  1625. // draw markings
  1626. var markings = options.grid.markings;
  1627. if (markings) {
  1628. if ($.isFunction(markings)) {
  1629. axes = plot.getAxes();
  1630. // xmin etc. is backwards compatibility, to be
  1631. // removed in the future
  1632. axes.xmin = axes.xaxis.min;
  1633. axes.xmax = axes.xaxis.max;
  1634. axes.ymin = axes.yaxis.min;
  1635. axes.ymax = axes.yaxis.max;
  1636. markings = markings(axes);
  1637. }
  1638. for (i = 0; i < markings.length; ++i) {
  1639. var m = markings[i],
  1640. xrange = extractRange(m, "x"),
  1641. yrange = extractRange(m, "y");
  1642. // fill in missing
  1643. if (xrange.from == null)
  1644. xrange.from = xrange.axis.min;
  1645. if (xrange.to == null)
  1646. xrange.to = xrange.axis.max;
  1647. if (yrange.from == null)
  1648. yrange.from = yrange.axis.min;
  1649. if (yrange.to == null)
  1650. yrange.to = yrange.axis.max;
  1651. // clip
  1652. if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
  1653. yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
  1654. continue;
  1655. xrange.from = Math.max(xrange.from, xrange.axis.min);
  1656. xrange.to = Math.min(xrange.to, xrange.axis.max);
  1657. yrange.from = Math.max(yrange.from, yrange.axis.min);
  1658. yrange.to = Math.min(yrange.to, yrange.axis.max);
  1659. var xequal = xrange.from === xrange.to,
  1660. yequal = yrange.from === yrange.to;
  1661. if (xequal && yequal) {
  1662. continue;
  1663. }
  1664. // then draw
  1665. xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
  1666. xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
  1667. yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
  1668. yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
  1669. if (xequal || yequal) {
  1670. var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
  1671. subPixel = lineWidth % 2 ? 0.5 : 0;
  1672. ctx.beginPath();
  1673. ctx.strokeStyle = m.color || options.grid.markingsColor;
  1674. ctx.lineWidth = lineWidth;
  1675. if (xequal) {
  1676. ctx.moveTo(xrange.to + subPixel, yrange.from);
  1677. ctx.lineTo(xrange.to + subPixel, yrange.to);
  1678. } else {
  1679. ctx.moveTo(xrange.from, yrange.to + subPixel);
  1680. ctx.lineTo(xrange.to, yrange.to + subPixel);
  1681. }
  1682. ctx.stroke();
  1683. } else {
  1684. ctx.fillStyle = m.color || options.grid.markingsColor;
  1685. ctx.fillRect(xrange.from, yrange.to,
  1686. xrange.to - xrange.from,
  1687. yrange.from - yrange.to);
  1688. }
  1689. }
  1690. }
  1691. // draw the ticks
  1692. axes = allAxes();
  1693. bw = options.grid.borderWidth;
  1694. for (var j = 0; j < axes.length; ++j) {
  1695. var axis = axes[j], box = axis.box,
  1696. t = axis.tickLength, x, y, xoff, yoff;
  1697. if (!axis.show || axis.ticks.length == 0)
  1698. continue;
  1699. ctx.lineWidth = 1;
  1700. // find the edges
  1701. if (axis.direction == "x") {
  1702. x = 0;
  1703. if (t == "full")
  1704. y = (axis.position == "top" ? 0 : plotHeight);
  1705. else
  1706. y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
  1707. }
  1708. else {
  1709. y = 0;
  1710. if (t == "full")
  1711. x = (axis.position == "left" ? 0 : plotWidth);
  1712. else
  1713. x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
  1714. }
  1715. // draw tick bar
  1716. if (!axis.innermost) {
  1717. ctx.strokeStyle = axis.options.color;
  1718. ctx.beginPath();
  1719. xoff = yoff = 0;
  1720. if (axis.direction == "x")
  1721. xoff = plotWidth + 1;
  1722. else
  1723. yoff = plotHeight + 1;
  1724. if (ctx.lineWidth == 1) {
  1725. if (axis.direction == "x") {
  1726. y = Math.floor(y) + 0.5;
  1727. } else {
  1728. x = Math.floor(x) + 0.5;
  1729. }
  1730. }
  1731. ctx.moveTo(x, y);
  1732. ctx.lineTo(x + xoff, y + yoff);
  1733. ctx.stroke();
  1734. }
  1735. // draw ticks
  1736. ctx.strokeStyle = axis.options.tickColor;
  1737. ctx.beginPath();
  1738. for (i = 0; i < axis.ticks.length; ++i) {
  1739. var v = axis.ticks[i].v;
  1740. xoff = yoff = 0;
  1741. if (isNaN(v) || v < axis.min || v > axis.max
  1742. // skip those lying on the axes if we got a border
  1743. || (t == "full"
  1744. && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
  1745. && (v == axis.min || v == axis.max)))
  1746. continue;
  1747. if (axis.direction == "x") {
  1748. x = axis.p2c(v);
  1749. yoff = t == "full" ? -plotHeight : t;
  1750. if (axis.position == "top")
  1751. yoff = -yoff;
  1752. }
  1753. else {
  1754. y = axis.p2c(v);
  1755. xoff = t == "full" ? -plotWidth : t;
  1756. if (axis.position == "left")
  1757. xoff = -xoff;
  1758. }
  1759. if (ctx.lineWidth == 1) {
  1760. if (axis.direction == "x")
  1761. x = Math.floor(x) + 0.5;
  1762. else
  1763. y = Math.floor(y) + 0.5;
  1764. }
  1765. ctx.moveTo(x, y);
  1766. ctx.lineTo(x + xoff, y + yoff);
  1767. }
  1768. ctx.stroke();
  1769. }
  1770. // draw border
  1771. if (bw) {
  1772. // If either borderWidth or borderColor is an object, then draw the border
  1773. // line by line instead of as one rectangle
  1774. bc = options.grid.borderColor;
  1775. if(typeof bw == "object" || typeof bc == "object") {
  1776. if (typeof bw !== "object") {
  1777. bw = {top: bw, right: bw, bottom: bw, left: bw};
  1778. }
  1779. if (typeof bc !== "object") {
  1780. bc = {top: bc, right: bc, bottom: bc, left: bc};
  1781. }
  1782. if (bw.top > 0) {
  1783. ctx.strokeStyle = bc.top;
  1784. ctx.lineWidth = bw.top;
  1785. ctx.beginPath();
  1786. ctx.moveTo(0 - bw.left, 0 - bw.top/2);
  1787. ctx.lineTo(plotWidth, 0 - bw.top/2);
  1788. ctx.stroke();
  1789. }
  1790. if (bw.right > 0) {
  1791. ctx.strokeStyle = bc.right;
  1792. ctx.lineWidth = bw.right;
  1793. ctx.beginPath();
  1794. ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
  1795. ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
  1796. ctx.stroke();
  1797. }
  1798. if (bw.bottom > 0) {
  1799. ctx.strokeStyle = bc.bottom;
  1800. ctx.lineWidth = bw.bottom;
  1801. ctx.beginPath();
  1802. ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
  1803. ctx.lineTo(0, plotHeight + bw.bottom / 2);
  1804. ctx.stroke();
  1805. }
  1806. if (bw.left > 0) {
  1807. ctx.strokeStyle = bc.left;
  1808. ctx.lineWidth = bw.left;
  1809. ctx.beginPath();
  1810. ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
  1811. ctx.lineTo(0- bw.left/2, 0);
  1812. ctx.stroke();
  1813. }
  1814. }
  1815. else {
  1816. ctx.lineWidth = bw;
  1817. ctx.strokeStyle = options.grid.borderColor;
  1818. ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
  1819. }
  1820. }
  1821. ctx.restore();
  1822. }
  1823. function drawAxisLabels() {
  1824. $.each(allAxes(), function (_, axis) {
  1825. var box = axis.box,
  1826. legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
  1827. layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
  1828. font = axis.options.font || "flot-tick-label tickLabel",
  1829. tick, x, y, halign, valign;
  1830. // Remove text before checking for axis.show and ticks.length;
  1831. // otherwise plugins, like flot-tickrotor, that draw their own
  1832. // tick labels will end up with both theirs and the defaults.
  1833. surface.removeText(layer);
  1834. if (!axis.show || axis.ticks.length == 0)
  1835. return;
  1836. for (var i = 0; i < axis.ticks.length; ++i) {
  1837. tick = axis.ticks[i];
  1838. if (!tick.label || tick.v < axis.min || tick.v > axis.max)
  1839. continue;
  1840. if (axis.direction == "x") {
  1841. halign = "center";
  1842. x = plotOffset.left + axis.p2c(tick.v);
  1843. if (axis.position == "bottom") {
  1844. y = box.top + box.padding;
  1845. } else {
  1846. y = box.top + box.height - box.padding;
  1847. valign = "bottom";
  1848. }
  1849. } else {
  1850. valign = "middle";
  1851. y = plotOffset.top + axis.p2c(tick.v);
  1852. if (axis.position == "left") {
  1853. x = box.left + box.width - box.padding;
  1854. halign = "right";
  1855. } else {
  1856. x = box.left + box.padding;
  1857. }
  1858. }
  1859. surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
  1860. }
  1861. });
  1862. }
  1863. function drawSeries(series) {
  1864. if (series.lines.show)
  1865. drawSeriesLines(series);
  1866. if (series.bars.show)
  1867. drawSeriesBars(series);
  1868. if (series.points.show)
  1869. drawSeriesPoints(series);
  1870. }
  1871. function drawSeriesLines(series) {
  1872. function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
  1873. var points = datapoints.points,
  1874. ps = datapoints.pointsize,
  1875. prevx = null, prevy = null;
  1876. ctx.beginPath();
  1877. for (var i = ps; i < points.length; i += ps) {
  1878. var x1 = points[i - ps], y1 = points[i - ps + 1],
  1879. x2 = points[i], y2 = points[i + 1];
  1880. if (x1 == null || x2 == null)
  1881. continue;
  1882. // clip with ymin
  1883. if (y1 <= y2 && y1 < axisy.min) {
  1884. if (y2 < axisy.min)
  1885. continue; // line segment is outside
  1886. // compute new intersection point
  1887. x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1888. y1 = axisy.min;
  1889. }
  1890. else if (y2 <= y1 && y2 < axisy.min) {
  1891. if (y1 < axisy.min)
  1892. continue;
  1893. x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  1894. y2 = axisy.min;
  1895. }
  1896. // clip with ymax
  1897. if (y1 >= y2 && y1 > axisy.max) {
  1898. if (y2 > axisy.max)
  1899. continue;
  1900. x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1901. y1 = axisy.max;
  1902. }
  1903. else if (y2 >= y1 && y2 > axisy.max) {
  1904. if (y1 > axisy.max)
  1905. continue;
  1906. x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  1907. y2 = axisy.max;
  1908. }
  1909. // clip with xmin
  1910. if (x1 <= x2 && x1 < axisx.min) {
  1911. if (x2 < axisx.min)
  1912. continue;
  1913. y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1914. x1 = axisx.min;
  1915. }
  1916. else if (x2 <= x1 && x2 < axisx.min) {
  1917. if (x1 < axisx.min)
  1918. continue;
  1919. y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1920. x2 = axisx.min;
  1921. }
  1922. // clip with xmax
  1923. if (x1 >= x2 && x1 > axisx.max) {
  1924. if (x2 > axisx.max)
  1925. continue;
  1926. y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1927. x1 = axisx.max;
  1928. }
  1929. else if (x2 >= x1 && x2 > axisx.max) {
  1930. if (x1 > axisx.max)
  1931. continue;
  1932. y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1933. x2 = axisx.max;
  1934. }
  1935. if (x1 != prevx || y1 != prevy)
  1936. ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
  1937. prevx = x2;
  1938. prevy = y2;
  1939. ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
  1940. }
  1941. ctx.stroke();
  1942. }
  1943. function plotLineArea(datapoints, axisx, axisy) {
  1944. var points = datapoints.points,
  1945. ps = datapoints.pointsize,
  1946. bottom = Math.min(Math.max(0, axisy.min), axisy.max),
  1947. i = 0, top, areaOpen = false,
  1948. ypos = 1, segmentStart = 0, segmentEnd = 0;
  1949. // we process each segment in two turns, first forward
  1950. // direction to sketch out top, then once we hit the
  1951. // end we go backwards to sketch the bottom
  1952. while (true) {
  1953. if (ps > 0 && i > points.length + ps)
  1954. break;
  1955. i += ps; // ps is negative if going backwards
  1956. var x1 = points[i - ps],
  1957. y1 = points[i - ps + ypos],
  1958. x2 = points[i], y2 = points[i + ypos];
  1959. if (areaOpen) {
  1960. if (ps > 0 && x1 != null && x2 == null) {
  1961. // at turning point
  1962. segmentEnd = i;
  1963. ps = -ps;
  1964. ypos = 2;
  1965. continue;
  1966. }
  1967. if (ps < 0 && i == segmentStart + ps) {
  1968. // done with the reverse sweep
  1969. ctx.fill();
  1970. areaOpen = false;
  1971. ps = -ps;
  1972. ypos = 1;
  1973. i = segmentStart = segmentEnd + ps;
  1974. continue;
  1975. }
  1976. }
  1977. if (x1 == null || x2 == null)
  1978. continue;
  1979. // clip x values
  1980. // clip with xmin
  1981. if (x1 <= x2 && x1 < axisx.min) {
  1982. if (x2 < axisx.min)
  1983. continue;
  1984. y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1985. x1 = axisx.min;
  1986. }
  1987. else if (x2 <= x1 && x2 < axisx.min) {
  1988. if (x1 < axisx.min)
  1989. continue;
  1990. y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  1991. x2 = axisx.min;
  1992. }
  1993. // clip with xmax
  1994. if (x1 >= x2 && x1 > axisx.max) {
  1995. if (x2 > axisx.max)
  1996. continue;
  1997. y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  1998. x1 = axisx.max;
  1999. }
  2000. else if (x2 >= x1 && x2 > axisx.max) {
  2001. if (x1 > axisx.max)
  2002. continue;
  2003. y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  2004. x2 = axisx.max;
  2005. }
  2006. if (!areaOpen) {
  2007. // open area
  2008. ctx.beginPath();
  2009. ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
  2010. areaOpen = true;
  2011. }
  2012. // now first check the case where both is outside
  2013. if (y1 >= axisy.max && y2 >= axisy.max) {
  2014. ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
  2015. ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
  2016. continue;
  2017. }
  2018. else if (y1 <= axisy.min && y2 <= axisy.min) {
  2019. ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
  2020. ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
  2021. continue;
  2022. }
  2023. // else it's a bit more complicated, there might
  2024. // be a flat maxed out rectangle first, then a
  2025. // triangular cutout or reverse; to find these
  2026. // keep track of the current x values
  2027. var x1old = x1, x2old = x2;
  2028. // clip the y values, without shortcutting, we
  2029. // go through all cases in turn
  2030. // clip with ymin
  2031. if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
  2032. x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  2033. y1 = axisy.min;
  2034. }
  2035. else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
  2036. x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  2037. y2 = axisy.min;
  2038. }
  2039. // clip with ymax
  2040. if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
  2041. x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  2042. y1 = axisy.max;
  2043. }
  2044. else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
  2045. x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  2046. y2 = axisy.max;
  2047. }
  2048. // if the x value was changed we got a rectangle
  2049. // to fill
  2050. if (x1 != x1old) {
  2051. ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
  2052. // it goes to (x1, y1), but we fill that below
  2053. }
  2054. // fill triangular section, this sometimes result
  2055. // in redundant points if (x1, y1) hasn't changed
  2056. // from previous line to, but we just ignore that
  2057. ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
  2058. ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  2059. // fill the other rectangle if it's there
  2060. if (x2 != x2old) {
  2061. ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  2062. ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
  2063. }
  2064. }
  2065. }
  2066. ctx.save();
  2067. ctx.translate(plotOffset.left, plotOffset.top);
  2068. ctx.lineJoin = "round";
  2069. var lw = series.lines.lineWidth,
  2070. sw = series.shadowSize;
  2071. // FIXME: consider another form of shadow when filling is turned on
  2072. if (lw > 0 && sw > 0) {
  2073. // draw shadow as a thick and thin line with transparency
  2074. ctx.lineWidth = sw;
  2075. ctx.strokeStyle = "rgba(0,0,0,0.1)";
  2076. // position shadow at angle from the mid of line
  2077. var angle = Math.PI/18;
  2078. plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
  2079. ctx.lineWidth = sw/2;
  2080. plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
  2081. }
  2082. ctx.lineWidth = lw;
  2083. ctx.strokeStyle = series.color;
  2084. var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
  2085. if (fillStyle) {
  2086. ctx.fillStyle = fillStyle;
  2087. plotLineArea(series.datapoints, series.xaxis, series.yaxis);
  2088. }
  2089. if (lw > 0)
  2090. plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
  2091. ctx.restore();
  2092. }
  2093. function drawSeriesPoints(series) {
  2094. function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
  2095. var points = datapoints.points, ps = datapoints.pointsize;
  2096. for (var i = 0; i < points.length; i += ps) {
  2097. var x = points[i], y = points[i + 1];
  2098. if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
  2099. continue;
  2100. ctx.beginPath();
  2101. x = axisx.p2c(x);
  2102. y = axisy.p2c(y) + offset;
  2103. if (symbol == "circle")
  2104. ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
  2105. else
  2106. symbol(ctx, x, y, radius, shadow);
  2107. ctx.closePath();
  2108. if (fillStyle) {
  2109. ctx.fillStyle = fillStyle;
  2110. ctx.fill();
  2111. }
  2112. ctx.stroke();
  2113. }
  2114. }
  2115. ctx.save();
  2116. ctx.translate(plotOffset.left, plotOffset.top);
  2117. var lw = series.points.lineWidth,
  2118. sw = series.shadowSize,
  2119. radius = series.points.radius,
  2120. symbol = series.points.symbol;
  2121. // If the user sets the line width to 0, we change it to a very
  2122. // small value. A line width of 0 seems to force the default of 1.
  2123. // Doing the conditional here allows the shadow setting to still be
  2124. // optional even with a lineWidth of 0.
  2125. if( lw == 0 )
  2126. lw = 0.0001;
  2127. if (lw > 0 && sw > 0) {
  2128. // draw shadow in two steps
  2129. var w = sw / 2;
  2130. ctx.lineWidth = w;
  2131. ctx.strokeStyle = "rgba(0,0,0,0.1)";
  2132. plotPoints(series.datapoints, radius, null, w + w/2, true,
  2133. series.xaxis, series.yaxis, symbol);
  2134. ctx.strokeStyle = "rgba(0,0,0,0.2)";
  2135. plotPoints(series.datapoints, radius, null, w/2, true,
  2136. series.xaxis, series.yaxis, symbol);
  2137. }
  2138. ctx.lineWidth = lw;
  2139. ctx.strokeStyle = series.color;
  2140. plotPoints(series.datapoints, radius,
  2141. getFillStyle(series.points, series.color), 0, false,
  2142. series.xaxis, series.yaxis, symbol);
  2143. ctx.restore();
  2144. }
  2145. function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
  2146. var left, right, bottom, top,
  2147. drawLeft, drawRight, drawTop, drawBottom,
  2148. tmp;
  2149. // in horizontal mode, we start the bar from the left
  2150. // instead of from the bottom so it appears to be
  2151. // horizontal rather than vertical
  2152. if (horizontal) {
  2153. drawBottom = drawRight = drawTop = true;
  2154. drawLeft = false;
  2155. left = b;
  2156. right = x;
  2157. top = y + barLeft;
  2158. bottom = y + barRight;
  2159. // account for negative bars
  2160. if (right < left) {
  2161. tmp = right;
  2162. right = left;
  2163. left = tmp;
  2164. drawLeft = true;
  2165. drawRight = false;
  2166. }
  2167. }
  2168. else {
  2169. drawLeft = drawRight = drawTop = true;
  2170. drawBottom = false;
  2171. left = x + barLeft;
  2172. right = x + barRight;
  2173. bottom = b;
  2174. top = y;
  2175. // account for negative bars
  2176. if (top < bottom) {
  2177. tmp = top;
  2178. top = bottom;
  2179. bottom = tmp;
  2180. drawBottom = true;
  2181. drawTop = false;
  2182. }
  2183. }
  2184. // clip
  2185. if (right < axisx.min || left > axisx.max ||
  2186. top < axisy.min || bottom > axisy.max)
  2187. return;
  2188. if (left < axisx.min) {
  2189. left = axisx.min;
  2190. drawLeft = false;
  2191. }
  2192. if (right > axisx.max) {
  2193. right = axisx.max;
  2194. drawRight = false;
  2195. }
  2196. if (bottom < axisy.min) {
  2197. bottom = axisy.min;
  2198. drawBottom = false;
  2199. }
  2200. if (top > axisy.max) {
  2201. top = axisy.max;
  2202. drawTop = false;
  2203. }
  2204. left = axisx.p2c(left);
  2205. bottom = axisy.p2c(bottom);
  2206. right = axisx.p2c(right);
  2207. top = axisy.p2c(top);
  2208. // fill the bar
  2209. if (fillStyleCallback) {
  2210. c.fillStyle = fillStyleCallback(bottom, top);
  2211. c.fillRect(left, top, right - left, bottom - top)
  2212. }
  2213. // draw outline
  2214. if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
  2215. c.beginPath();
  2216. // FIXME: inline moveTo is buggy with excanvas
  2217. c.moveTo(left, bottom);
  2218. if (drawLeft)
  2219. c.lineTo(left, top);
  2220. else
  2221. c.moveTo(left, top);
  2222. if (drawTop)
  2223. c.lineTo(right, top);
  2224. else
  2225. c.moveTo(right, top);
  2226. if (drawRight)
  2227. c.lineTo(right, bottom);
  2228. else
  2229. c.moveTo(right, bottom);
  2230. if (drawBottom)
  2231. c.lineTo(left, bottom);
  2232. else
  2233. c.moveTo(left, bottom);
  2234. c.stroke();
  2235. }
  2236. }
  2237. function drawSeriesBars(series) {
  2238. function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
  2239. var points = datapoints.points, ps = datapoints.pointsize;
  2240. for (var i = 0; i < points.length; i += ps) {
  2241. if (points[i] == null)
  2242. continue;
  2243. drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
  2244. }
  2245. }
  2246. ctx.save();
  2247. ctx.translate(plotOffset.left, plotOffset.top);
  2248. // FIXME: figure out a way to add shadows (for instance along the right edge)
  2249. ctx.lineWidth = series.bars.lineWidth;
  2250. ctx.strokeStyle = series.color;
  2251. var barLeft;
  2252. switch (series.bars.align) {
  2253. case "left":
  2254. barLeft = 0;
  2255. break;
  2256. case "right":
  2257. barLeft = -series.bars.barWidth;
  2258. break;
  2259. default:
  2260. barLeft = -series.bars.barWidth / 2;
  2261. }
  2262. var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
  2263. plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
  2264. ctx.restore();
  2265. }
  2266. function getFillStyle(filloptions, seriesColor, bottom, top) {
  2267. var fill = filloptions.fill;
  2268. if (!fill)
  2269. return null;
  2270. if (filloptions.fillColor)
  2271. return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
  2272. var c = $.color.parse(seriesColor);
  2273. c.a = typeof fill == "number" ? fill : 0.4;
  2274. c.normalize();
  2275. return c.toString();
  2276. }
  2277. function insertLegend() {
  2278. if (options.legend.container != null) {
  2279. $(options.legend.container).html("");
  2280. } else {
  2281. placeholder.find(".legend").remove();
  2282. }
  2283. if (!options.legend.show) {
  2284. return;
  2285. }
  2286. var fragments = [], entries = [], rowStarted = false,
  2287. lf = options.legend.labelFormatter, s, label;
  2288. // Build a list of legend entries, with each having a label and a color
  2289. for (var i = 0; i < series.length; ++i) {
  2290. s = series[i];
  2291. if (s.label) {
  2292. label = lf ? lf(s.label, s) : s.label;
  2293. if (label) {
  2294. entries.push({
  2295. label: label,
  2296. color: s.color
  2297. });
  2298. }
  2299. }
  2300. }
  2301. // Sort the legend using either the default or a custom comparator
  2302. if (options.legend.sorted) {
  2303. if ($.isFunction(options.legend.sorted)) {
  2304. entries.sort(options.legend.sorted);
  2305. } else if (options.legend.sorted == "reverse") {
  2306. entries.reverse();
  2307. } else {
  2308. var ascending = options.legend.sorted != "descending";
  2309. entries.sort(function(a, b) {
  2310. return a.label == b.label ? 0 : (
  2311. (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
  2312. );
  2313. });
  2314. }
  2315. }
  2316. // Generate markup for the list of entries, in their final order
  2317. for (var i = 0; i < entries.length; ++i) {
  2318. var entry = entries[i];
  2319. if (i % options.legend.noColumns == 0) {
  2320. if (rowStarted)
  2321. fragments.push('</tr>');
  2322. fragments.push('<tr>');
  2323. rowStarted = true;
  2324. }
  2325. fragments.push(
  2326. '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
  2327. '<td class="legendLabel">' + entry.label + '</td>'
  2328. );
  2329. }
  2330. if (rowStarted)
  2331. fragments.push('</tr>');
  2332. if (fragments.length == 0)
  2333. return;
  2334. var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
  2335. if (options.legend.container != null)
  2336. $(options.legend.container).html(table);
  2337. else {
  2338. var pos = "",
  2339. p = options.legend.position,
  2340. m = options.legend.margin;
  2341. if (m[0] == null)
  2342. m = [m, m];
  2343. if (p.charAt(0) == "n")
  2344. pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
  2345. else if (p.charAt(0) == "s")
  2346. pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
  2347. if (p.charAt(1) == "e")
  2348. pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
  2349. else if (p.charAt(1) == "w")
  2350. pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
  2351. var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
  2352. if (options.legend.backgroundOpacity != 0.0) {
  2353. // put in the transparent background
  2354. // separately to avoid blended labels and
  2355. // label boxes
  2356. var c = options.legend.backgroundColor;
  2357. if (c == null) {
  2358. c = options.grid.backgroundColor;
  2359. if (c && typeof c == "string")
  2360. c = $.color.parse(c);
  2361. else
  2362. c = $.color.extract(legend, 'background-color');
  2363. c.a = 1;
  2364. c = c.toString();
  2365. }
  2366. var div = legend.children();
  2367. $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
  2368. }
  2369. }
  2370. }
  2371. // interactive features
  2372. var highlights = [],
  2373. redrawTimeout = null;
  2374. // returns the data item the mouse is over, or null if none is found
  2375. function findNearbyItem(mouseX, mouseY, seriesFilter) {
  2376. var maxDistance = options.grid.mouseActiveRadius,
  2377. smallestDistance = maxDistance * maxDistance + 1,
  2378. item = null, foundPoint = false, i, j, ps;
  2379. for (i = series.length - 1; i >= 0; --i) {
  2380. if (!seriesFilter(series[i]))
  2381. continue;
  2382. var s = series[i],
  2383. axisx = s.xaxis,
  2384. axisy = s.yaxis,
  2385. points = s.datapoints.points,
  2386. mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
  2387. my = axisy.c2p(mouseY),
  2388. maxx = maxDistance / axisx.scale,
  2389. maxy = maxDistance / axisy.scale;
  2390. ps = s.datapoints.pointsize;
  2391. // with inverse transforms, we can't use the maxx/maxy
  2392. // optimization, sadly
  2393. if (axisx.options.inverseTransform)
  2394. maxx = Number.MAX_VALUE;
  2395. if (axisy.options.inverseTransform)
  2396. maxy = Number.MAX_VALUE;
  2397. if (s.lines.show || s.points.show) {
  2398. for (j = 0; j < points.length; j += ps) {
  2399. var x = points[j], y = points[j + 1];
  2400. if (x == null)
  2401. continue;
  2402. // For points and lines, the cursor must be within a
  2403. // certain distance to the data point
  2404. if (x - mx > maxx || x - mx < -maxx ||
  2405. y - my > maxy || y - my < -maxy)
  2406. continue;
  2407. // We have to calculate distances in pixels, not in
  2408. // data units, because the scales of the axes may be different
  2409. var dx = Math.abs(axisx.p2c(x) - mouseX),
  2410. dy = Math.abs(axisy.p2c(y) - mouseY),
  2411. dist = dx * dx + dy * dy; // we save the sqrt
  2412. // use <= to ensure last point takes precedence
  2413. // (last generally means on top of)
  2414. if (dist < smallestDistance) {
  2415. smallestDistance = dist;
  2416. item = [i, j / ps];
  2417. }
  2418. }
  2419. }
  2420. if (s.bars.show && !item) { // no other point can be nearby
  2421. var barLeft, barRight;
  2422. switch (s.bars.align) {
  2423. case "left":
  2424. barLeft = 0;
  2425. break;
  2426. case "right":
  2427. barLeft = -s.bars.barWidth;
  2428. break;
  2429. default:
  2430. barLeft = -s.bars.barWidth / 2;
  2431. }
  2432. barRight = barLeft + s.bars.barWidth;
  2433. for (j = 0; j < points.length; j += ps) {
  2434. var x = points[j], y = points[j + 1], b = points[j + 2];
  2435. if (x == null)
  2436. continue;
  2437. // for a bar graph, the cursor must be inside the bar
  2438. if (series[i].bars.horizontal ?
  2439. (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
  2440. my >= y + barLeft && my <= y + barRight) :
  2441. (mx >= x + barLeft && mx <= x + barRight &&
  2442. my >= Math.min(b, y) && my <= Math.max(b, y)))
  2443. item = [i, j / ps];
  2444. }
  2445. }
  2446. }
  2447. if (item) {
  2448. i = item[0];
  2449. j = item[1];
  2450. ps = series[i].datapoints.pointsize;
  2451. return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
  2452. dataIndex: j,
  2453. series: series[i],
  2454. seriesIndex: i };
  2455. }
  2456. return null;
  2457. }
  2458. function onMouseMove(e) {
  2459. if (options.grid.hoverable)
  2460. triggerClickHoverEvent("plothover", e,
  2461. function (s) { return s["hoverable"] != false; });
  2462. }
  2463. function onMouseLeave(e) {
  2464. if (options.grid.hoverable)
  2465. triggerClickHoverEvent("plothover", e,
  2466. function (s) { return false; });
  2467. }
  2468. function onClick(e) {
  2469. triggerClickHoverEvent("plotclick", e,
  2470. function (s) { return s["clickable"] != false; });
  2471. }
  2472. // trigger click or hover event (they send the same parameters
  2473. // so we share their code)
  2474. function triggerClickHoverEvent(eventname, event, seriesFilter) {
  2475. var offset = eventHolder.offset(),
  2476. canvasX = event.pageX - offset.left - plotOffset.left,
  2477. canvasY = event.pageY - offset.top - plotOffset.top,
  2478. pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
  2479. pos.pageX = event.pageX;
  2480. pos.pageY = event.pageY;
  2481. var item = findNearbyItem(canvasX, canvasY, seriesFilter);
  2482. if (item) {
  2483. // fill in mouse pos for any listeners out there
  2484. item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
  2485. item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
  2486. }
  2487. if (options.grid.autoHighlight) {
  2488. // clear auto-highlights
  2489. for (var i = 0; i < highlights.length; ++i) {
  2490. var h = highlights[i];
  2491. if (h.auto == eventname &&
  2492. !(item && h.series == item.series &&
  2493. h.point[0] == item.datapoint[0] &&
  2494. h.point[1] == item.datapoint[1]))
  2495. unhighlight(h.series, h.point);
  2496. }
  2497. if (item)
  2498. highlight(item.series, item.datapoint, eventname);
  2499. }
  2500. placeholder.trigger(eventname, [ pos, item ]);
  2501. }
  2502. function triggerRedrawOverlay() {
  2503. var t = options.interaction.redrawOverlayInterval;
  2504. if (t == -1) { // skip event queue
  2505. drawOverlay();
  2506. return;
  2507. }
  2508. if (!redrawTimeout)
  2509. redrawTimeout = setTimeout(drawOverlay, t);
  2510. }
  2511. function drawOverlay() {
  2512. redrawTimeout = null;
  2513. // draw highlights
  2514. octx.save();
  2515. overlay.clear();
  2516. octx.translate(plotOffset.left, plotOffset.top);
  2517. var i, hi;
  2518. for (i = 0; i < highlights.length; ++i) {
  2519. hi = highlights[i];
  2520. if (hi.series.bars.show)
  2521. drawBarHighlight(hi.series, hi.point);
  2522. else
  2523. drawPointHighlight(hi.series, hi.point);
  2524. }
  2525. octx.restore();
  2526. executeHooks(hooks.drawOverlay, [octx]);
  2527. }
  2528. function highlight(s, point, auto) {
  2529. if (typeof s == "number")
  2530. s = series[s];
  2531. if (typeof point == "number") {
  2532. var ps = s.datapoints.pointsize;
  2533. point = s.datapoints.points.slice(ps * point, ps * (point + 1));
  2534. }
  2535. var i = indexOfHighlight(s, point);
  2536. if (i == -1) {
  2537. highlights.push({ series: s, point: point, auto: auto });
  2538. triggerRedrawOverlay();
  2539. }
  2540. else if (!auto)
  2541. highlights[i].auto = false;
  2542. }
  2543. function unhighlight(s, point) {
  2544. if (s == null && point == null) {
  2545. highlights = [];
  2546. triggerRedrawOverlay();
  2547. return;
  2548. }
  2549. if (typeof s == "number")
  2550. s = series[s];
  2551. if (typeof point == "number") {
  2552. var ps = s.datapoints.pointsize;
  2553. point = s.datapoints.points.slice(ps * point, ps * (point + 1));
  2554. }
  2555. var i = indexOfHighlight(s, point);
  2556. if (i != -1) {
  2557. highlights.splice(i, 1);
  2558. triggerRedrawOverlay();
  2559. }
  2560. }
  2561. function indexOfHighlight(s, p) {
  2562. for (var i = 0; i < highlights.length; ++i) {
  2563. var h = highlights[i];
  2564. if (h.series == s && h.point[0] == p[0]
  2565. && h.point[1] == p[1])
  2566. return i;
  2567. }
  2568. return -1;
  2569. }
  2570. function drawPointHighlight(series, point) {
  2571. var x = point[0], y = point[1],
  2572. axisx = series.xaxis, axisy = series.yaxis,
  2573. highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
  2574. if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
  2575. return;
  2576. var pointRadius = series.points.radius + series.points.lineWidth / 2;
  2577. octx.lineWidth = pointRadius;
  2578. octx.strokeStyle = highlightColor;
  2579. var radius = 1.5 * pointRadius;
  2580. x = axisx.p2c(x);
  2581. y = axisy.p2c(y);
  2582. octx.beginPath();
  2583. if (series.points.symbol == "circle")
  2584. octx.arc(x, y, radius, 0, 2 * Math.PI, false);
  2585. else
  2586. series.points.symbol(octx, x, y, radius, false);
  2587. octx.closePath();
  2588. octx.stroke();
  2589. }
  2590. function drawBarHighlight(series, point) {
  2591. var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
  2592. fillStyle = highlightColor,
  2593. barLeft;
  2594. switch (series.bars.align) {
  2595. case "left":
  2596. barLeft = 0;
  2597. break;
  2598. case "right":
  2599. barLeft = -series.bars.barWidth;
  2600. break;
  2601. default:
  2602. barLeft = -series.bars.barWidth / 2;
  2603. }
  2604. octx.lineWidth = series.bars.lineWidth;
  2605. octx.strokeStyle = highlightColor;
  2606. drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
  2607. function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
  2608. }
  2609. function getColorOrGradient(spec, bottom, top, defaultColor) {
  2610. if (typeof spec == "string")
  2611. return spec;
  2612. else {
  2613. // assume this is a gradient spec; IE currently only
  2614. // supports a simple vertical gradient properly, so that's
  2615. // what we support too
  2616. var gradient = ctx.createLinearGradient(0, top, 0, bottom);
  2617. for (var i = 0, l = spec.colors.length; i < l; ++i) {
  2618. var c = spec.colors[i];
  2619. if (typeof c != "string") {
  2620. var co = $.color.parse(defaultColor);
  2621. if (c.brightness != null)
  2622. co = co.scale('rgb', c.brightness);
  2623. if (c.opacity != null)
  2624. co.a *= c.opacity;
  2625. c = co.toString();
  2626. }
  2627. gradient.addColorStop(i / (l - 1), c);
  2628. }
  2629. return gradient;
  2630. }
  2631. }
  2632. }
  2633. // Add the plot function to the top level of the jQuery object
  2634. $.plot = function(placeholder, data, options) {
  2635. //var t0 = new Date();
  2636. var plot = new Plot($(placeholder), data, options, $.plot.plugins);
  2637. //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
  2638. return plot;
  2639. };
  2640. $.plot.version = "0.8.3";
  2641. $.plot.plugins = [];
  2642. // Also add the plot function as a chainable property
  2643. $.fn.plot = function(data, options) {
  2644. return this.each(function() {
  2645. $.plot(this, data, options);
  2646. });
  2647. };
  2648. // round to nearby lower multiple of base
  2649. function floorInBase(n, base) {
  2650. return base * Math.floor(n / base);
  2651. }
  2652. })(jQuery);
  2653. </script>
  2654. <script type="application/javascript">/* Flot plugin for rendering pie charts.
  2655. Copyright (c) 2007-2014 IOLA and Ole Laursen.
  2656. Licensed under the MIT license.
  2657. The plugin assumes that each series has a single data value, and that each
  2658. value is a positive integer or zero. Negative numbers don't make sense for a
  2659. pie chart, and have unpredictable results. The values do NOT need to be
  2660. passed in as percentages; the plugin will calculate the total and per-slice
  2661. percentages internally.
  2662. * Created by Brian Medendorp
  2663. * Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
  2664. The plugin supports these options:
  2665. series: {
  2666. pie: {
  2667. show: true/false
  2668. radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
  2669. innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
  2670. startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
  2671. tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
  2672. offset: {
  2673. top: integer value to move the pie up or down
  2674. left: integer value to move the pie left or right, or 'auto'
  2675. },
  2676. stroke: {
  2677. color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
  2678. width: integer pixel width of the stroke
  2679. },
  2680. label: {
  2681. show: true/false, or 'auto'
  2682. formatter: a user-defined function that modifies the text/style of the label text
  2683. radius: 0-1 for percentage of fullsize, or a specified pixel length
  2684. background: {
  2685. color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
  2686. opacity: 0-1
  2687. },
  2688. threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
  2689. },
  2690. combine: {
  2691. threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
  2692. color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
  2693. label: any text value of what the combined slice should be labeled
  2694. }
  2695. highlight: {
  2696. opacity: 0-1
  2697. }
  2698. }
  2699. }
  2700. More detail and specific examples can be found in the included HTML file.
  2701. */
  2702. (function($) {
  2703. // Maximum redraw attempts when fitting labels within the plot
  2704. var REDRAW_ATTEMPTS = 10;
  2705. // Factor by which to shrink the pie when fitting labels within the plot
  2706. var REDRAW_SHRINK = 0.95;
  2707. function init(plot) {
  2708. var canvas = null,
  2709. target = null,
  2710. options = null,
  2711. maxRadius = null,
  2712. centerLeft = null,
  2713. centerTop = null,
  2714. processed = false,
  2715. ctx = null;
  2716. // interactive variables
  2717. var highlights = [];
  2718. // add hook to determine if pie plugin in enabled, and then perform necessary operations
  2719. plot.hooks.processOptions.push(function(plot, options) {
  2720. if (options.series.pie.show) {
  2721. options.grid.show = false;
  2722. // set labels.show
  2723. if (options.series.pie.label.show == "auto") {
  2724. if (options.legend.show) {
  2725. options.series.pie.label.show = false;
  2726. } else {
  2727. options.series.pie.label.show = true;
  2728. }
  2729. }
  2730. // set radius
  2731. if (options.series.pie.radius == "auto") {
  2732. if (options.series.pie.label.show) {
  2733. options.series.pie.radius = 3/4;
  2734. } else {
  2735. options.series.pie.radius = 1;
  2736. }
  2737. }
  2738. // ensure sane tilt
  2739. if (options.series.pie.tilt > 1) {
  2740. options.series.pie.tilt = 1;
  2741. } else if (options.series.pie.tilt < 0) {
  2742. options.series.pie.tilt = 0;
  2743. }
  2744. }
  2745. });
  2746. plot.hooks.bindEvents.push(function(plot, eventHolder) {
  2747. var options = plot.getOptions();
  2748. if (options.series.pie.show) {
  2749. if (options.grid.hoverable) {
  2750. eventHolder.unbind("mousemove").mousemove(onMouseMove);
  2751. }
  2752. if (options.grid.clickable) {
  2753. eventHolder.unbind("click").click(onClick);
  2754. }
  2755. }
  2756. });
  2757. plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
  2758. var options = plot.getOptions();
  2759. if (options.series.pie.show) {
  2760. processDatapoints(plot, series, data, datapoints);
  2761. }
  2762. });
  2763. plot.hooks.drawOverlay.push(function(plot, octx) {
  2764. var options = plot.getOptions();
  2765. if (options.series.pie.show) {
  2766. drawOverlay(plot, octx);
  2767. }
  2768. });
  2769. plot.hooks.draw.push(function(plot, newCtx) {
  2770. var options = plot.getOptions();
  2771. if (options.series.pie.show) {
  2772. draw(plot, newCtx);
  2773. }
  2774. });
  2775. function processDatapoints(plot, series, datapoints) {
  2776. if (!processed) {
  2777. processed = true;
  2778. canvas = plot.getCanvas();
  2779. target = $(canvas).parent();
  2780. options = plot.getOptions();
  2781. plot.setData(combine(plot.getData()));
  2782. }
  2783. }
  2784. function combine(data) {
  2785. var total = 0,
  2786. combined = 0,
  2787. numCombined = 0,
  2788. color = options.series.pie.combine.color,
  2789. newdata = [];
  2790. // Fix up the raw data from Flot, ensuring the data is numeric
  2791. for (var i = 0; i < data.length; ++i) {
  2792. var value = data[i].data;
  2793. // If the data is an array, we'll assume that it's a standard
  2794. // Flot x-y pair, and are concerned only with the second value.
  2795. // Note how we use the original array, rather than creating a
  2796. // new one; this is more efficient and preserves any extra data
  2797. // that the user may have stored in higher indexes.
  2798. if ($.isArray(value) && value.length == 1) {
  2799. value = value[0];
  2800. }
  2801. if ($.isArray(value)) {
  2802. // Equivalent to $.isNumeric() but compatible with jQuery < 1.7
  2803. if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
  2804. value[1] = +value[1];
  2805. } else {
  2806. value[1] = 0;
  2807. }
  2808. } else if (!isNaN(parseFloat(value)) && isFinite(value)) {
  2809. value = [1, +value];
  2810. } else {
  2811. value = [1, 0];
  2812. }
  2813. data[i].data = [value];
  2814. }
  2815. // Sum up all the slices, so we can calculate percentages for each
  2816. for (var i = 0; i < data.length; ++i) {
  2817. total += data[i].data[0][1];
  2818. }
  2819. // Count the number of slices with percentages below the combine
  2820. // threshold; if it turns out to be just one, we won't combine.
  2821. for (var i = 0; i < data.length; ++i) {
  2822. var value = data[i].data[0][1];
  2823. if (value / total <= options.series.pie.combine.threshold) {
  2824. combined += value;
  2825. numCombined++;
  2826. if (!color) {
  2827. color = data[i].color;
  2828. }
  2829. }
  2830. }
  2831. for (var i = 0; i < data.length; ++i) {
  2832. var value = data[i].data[0][1];
  2833. if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
  2834. newdata.push(
  2835. $.extend(data[i], { /* extend to allow keeping all other original data values
  2836. and using them e.g. in labelFormatter. */
  2837. data: [[1, value]],
  2838. color: data[i].color,
  2839. label: data[i].label,
  2840. angle: value * Math.PI * 2 / total,
  2841. percent: value / (total / 100)
  2842. })
  2843. );
  2844. }
  2845. }
  2846. if (numCombined > 1) {
  2847. newdata.push({
  2848. data: [[1, combined]],
  2849. color: color,
  2850. label: options.series.pie.combine.label,
  2851. angle: combined * Math.PI * 2 / total,
  2852. percent: combined / (total / 100)
  2853. });
  2854. }
  2855. return newdata;
  2856. }
  2857. function draw(plot, newCtx) {
  2858. if (!target) {
  2859. return; // if no series were passed
  2860. }
  2861. var canvasWidth = plot.getPlaceholder().width(),
  2862. canvasHeight = plot.getPlaceholder().height(),
  2863. legendWidth = target.children().filter(".legend").children().width() || 0;
  2864. ctx = newCtx;
  2865. // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
  2866. // When combining smaller slices into an 'other' slice, we need to
  2867. // add a new series. Since Flot gives plugins no way to modify the
  2868. // list of series, the pie plugin uses a hack where the first call
  2869. // to processDatapoints results in a call to setData with the new
  2870. // list of series, then subsequent processDatapoints do nothing.
  2871. // The plugin-global 'processed' flag is used to control this hack;
  2872. // it starts out false, and is set to true after the first call to
  2873. // processDatapoints.
  2874. // Unfortunately this turns future setData calls into no-ops; they
  2875. // call processDatapoints, the flag is true, and nothing happens.
  2876. // To fix this we'll set the flag back to false here in draw, when
  2877. // all series have been processed, so the next sequence of calls to
  2878. // processDatapoints once again starts out with a slice-combine.
  2879. // This is really a hack; in 0.9 we need to give plugins a proper
  2880. // way to modify series before any processing begins.
  2881. processed = false;
  2882. // calculate maximum radius and center point
  2883. maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
  2884. centerTop = canvasHeight / 2 + options.series.pie.offset.top;
  2885. centerLeft = canvasWidth / 2;
  2886. if (options.series.pie.offset.left == "auto") {
  2887. if (options.legend.position.match("w")) {
  2888. centerLeft += legendWidth / 2;
  2889. } else {
  2890. centerLeft -= legendWidth / 2;
  2891. }
  2892. if (centerLeft < maxRadius) {
  2893. centerLeft = maxRadius;
  2894. } else if (centerLeft > canvasWidth - maxRadius) {
  2895. centerLeft = canvasWidth - maxRadius;
  2896. }
  2897. } else {
  2898. centerLeft += options.series.pie.offset.left;
  2899. }
  2900. var slices = plot.getData(),
  2901. attempts = 0;
  2902. // Keep shrinking the pie's radius until drawPie returns true,
  2903. // indicating that all the labels fit, or we try too many times.
  2904. do {
  2905. if (attempts > 0) {
  2906. maxRadius *= REDRAW_SHRINK;
  2907. }
  2908. attempts += 1;
  2909. clear();
  2910. if (options.series.pie.tilt <= 0.8) {
  2911. drawShadow();
  2912. }
  2913. } while (!drawPie() && attempts < REDRAW_ATTEMPTS)
  2914. if (attempts >= REDRAW_ATTEMPTS) {
  2915. clear();
  2916. target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
  2917. }
  2918. if (plot.setSeries && plot.insertLegend) {
  2919. plot.setSeries(slices);
  2920. plot.insertLegend();
  2921. }
  2922. // we're actually done at this point, just defining internal functions at this point
  2923. function clear() {
  2924. ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  2925. target.children().filter(".pieLabel, .pieLabelBackground").remove();
  2926. }
  2927. function drawShadow() {
  2928. var shadowLeft = options.series.pie.shadow.left;
  2929. var shadowTop = options.series.pie.shadow.top;
  2930. var edge = 10;
  2931. var alpha = options.series.pie.shadow.alpha;
  2932. var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  2933. if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
  2934. return; // shadow would be outside canvas, so don't draw it
  2935. }
  2936. ctx.save();
  2937. ctx.translate(shadowLeft,shadowTop);
  2938. ctx.globalAlpha = alpha;
  2939. ctx.fillStyle = "#000";
  2940. // center and rotate to starting position
  2941. ctx.translate(centerLeft,centerTop);
  2942. ctx.scale(1, options.series.pie.tilt);
  2943. //radius -= edge;
  2944. for (var i = 1; i <= edge; i++) {
  2945. ctx.beginPath();
  2946. ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
  2947. ctx.fill();
  2948. radius -= i;
  2949. }
  2950. ctx.restore();
  2951. }
  2952. function drawPie() {
  2953. var startAngle = Math.PI * options.series.pie.startAngle;
  2954. var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  2955. // center and rotate to starting position
  2956. ctx.save();
  2957. ctx.translate(centerLeft,centerTop);
  2958. ctx.scale(1, options.series.pie.tilt);
  2959. //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
  2960. // draw slices
  2961. ctx.save();
  2962. var currentAngle = startAngle;
  2963. for (var i = 0; i < slices.length; ++i) {
  2964. slices[i].startAngle = currentAngle;
  2965. drawSlice(slices[i].angle, slices[i].color, true);
  2966. }
  2967. ctx.restore();
  2968. // draw slice outlines
  2969. if (options.series.pie.stroke.width > 0) {
  2970. ctx.save();
  2971. ctx.lineWidth = options.series.pie.stroke.width;
  2972. currentAngle = startAngle;
  2973. for (var i = 0; i < slices.length; ++i) {
  2974. drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
  2975. }
  2976. ctx.restore();
  2977. }
  2978. // draw donut hole
  2979. drawDonutHole(ctx);
  2980. ctx.restore();
  2981. // Draw the labels, returning true if they fit within the plot
  2982. if (options.series.pie.label.show) {
  2983. return drawLabels();
  2984. } else return true;
  2985. function drawSlice(angle, color, fill) {
  2986. if (angle <= 0 || isNaN(angle)) {
  2987. return;
  2988. }
  2989. if (fill) {
  2990. ctx.fillStyle = color;
  2991. } else {
  2992. ctx.strokeStyle = color;
  2993. ctx.lineJoin = "round";
  2994. }
  2995. ctx.beginPath();
  2996. if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
  2997. ctx.moveTo(0, 0); // Center of the pie
  2998. }
  2999. //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
  3000. ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
  3001. ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
  3002. ctx.closePath();
  3003. //ctx.rotate(angle); // This doesn't work properly in Opera
  3004. currentAngle += angle;
  3005. if (fill) {
  3006. ctx.fill();
  3007. } else {
  3008. ctx.stroke();
  3009. }
  3010. }
  3011. function drawLabels() {
  3012. var currentAngle = startAngle;
  3013. var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
  3014. for (var i = 0; i < slices.length; ++i) {
  3015. if (slices[i].percent >= options.series.pie.label.threshold * 100) {
  3016. if (!drawLabel(slices[i], currentAngle, i)) {
  3017. return false;
  3018. }
  3019. }
  3020. currentAngle += slices[i].angle;
  3021. }
  3022. return true;
  3023. function drawLabel(slice, startAngle, index) {
  3024. if (slice.data[0][1] == 0) {
  3025. return true;
  3026. }
  3027. // format label text
  3028. var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
  3029. if (lf) {
  3030. text = lf(slice.label, slice);
  3031. } else {
  3032. text = slice.label;
  3033. }
  3034. if (plf) {
  3035. text = plf(text, slice);
  3036. }
  3037. var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
  3038. var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
  3039. var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
  3040. var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
  3041. target.append(html);
  3042. var label = target.children("#pieLabel" + index);
  3043. var labelTop = (y - label.height() / 2);
  3044. var labelLeft = (x - label.width() / 2);
  3045. label.css("top", labelTop);
  3046. label.css("left", labelLeft);
  3047. // check to make sure that the label is not outside the canvas
  3048. if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
  3049. return false;
  3050. }
  3051. if (options.series.pie.label.background.opacity != 0) {
  3052. // put in the transparent background separately to avoid blended labels and label boxes
  3053. var c = options.series.pie.label.background.color;
  3054. if (c == null) {
  3055. c = slice.color;
  3056. }
  3057. var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
  3058. $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
  3059. .css("opacity", options.series.pie.label.background.opacity)
  3060. .insertBefore(label);
  3061. }
  3062. return true;
  3063. } // end individual label function
  3064. } // end drawLabels function
  3065. } // end drawPie function
  3066. } // end draw function
  3067. // Placed here because it needs to be accessed from multiple locations
  3068. function drawDonutHole(layer) {
  3069. if (options.series.pie.innerRadius > 0) {
  3070. // subtract the center
  3071. layer.save();
  3072. var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
  3073. layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
  3074. layer.beginPath();
  3075. layer.fillStyle = options.series.pie.stroke.color;
  3076. layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
  3077. layer.fill();
  3078. layer.closePath();
  3079. layer.restore();
  3080. // add inner stroke
  3081. layer.save();
  3082. layer.beginPath();
  3083. layer.strokeStyle = options.series.pie.stroke.color;
  3084. layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
  3085. layer.stroke();
  3086. layer.closePath();
  3087. layer.restore();
  3088. // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
  3089. }
  3090. }
  3091. //-- Additional Interactive related functions --
  3092. function isPointInPoly(poly, pt) {
  3093. for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
  3094. ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
  3095. && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
  3096. && (c = !c);
  3097. return c;
  3098. }
  3099. function findNearbySlice(mouseX, mouseY) {
  3100. var slices = plot.getData(),
  3101. options = plot.getOptions(),
  3102. radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
  3103. x, y;
  3104. for (var i = 0; i < slices.length; ++i) {
  3105. var s = slices[i];
  3106. if (s.pie.show) {
  3107. ctx.save();
  3108. ctx.beginPath();
  3109. ctx.moveTo(0, 0); // Center of the pie
  3110. //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
  3111. ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
  3112. ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
  3113. ctx.closePath();
  3114. x = mouseX - centerLeft;
  3115. y = mouseY - centerTop;
  3116. if (ctx.isPointInPath) {
  3117. if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
  3118. ctx.restore();
  3119. return {
  3120. datapoint: [s.percent, s.data],
  3121. dataIndex: 0,
  3122. series: s,
  3123. seriesIndex: i
  3124. };
  3125. }
  3126. } else {
  3127. // excanvas for IE doesn;t support isPointInPath, this is a workaround.
  3128. var p1X = radius * Math.cos(s.startAngle),
  3129. p1Y = radius * Math.sin(s.startAngle),
  3130. p2X = radius * Math.cos(s.startAngle + s.angle / 4),
  3131. p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
  3132. p3X = radius * Math.cos(s.startAngle + s.angle / 2),
  3133. p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
  3134. p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
  3135. p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
  3136. p5X = radius * Math.cos(s.startAngle + s.angle),
  3137. p5Y = radius * Math.sin(s.startAngle + s.angle),
  3138. arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
  3139. arrPoint = [x, y];
  3140. // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
  3141. if (isPointInPoly(arrPoly, arrPoint)) {
  3142. ctx.restore();
  3143. return {
  3144. datapoint: [s.percent, s.data],
  3145. dataIndex: 0,
  3146. series: s,
  3147. seriesIndex: i
  3148. };
  3149. }
  3150. }
  3151. ctx.restore();
  3152. }
  3153. }
  3154. return null;
  3155. }
  3156. function onMouseMove(e) {
  3157. triggerClickHoverEvent("plothover", e);
  3158. }
  3159. function onClick(e) {
  3160. triggerClickHoverEvent("plotclick", e);
  3161. }
  3162. // trigger click or hover event (they send the same parameters so we share their code)
  3163. function triggerClickHoverEvent(eventname, e) {
  3164. var offset = plot.offset();
  3165. var canvasX = parseInt(e.pageX - offset.left);
  3166. var canvasY = parseInt(e.pageY - offset.top);
  3167. var item = findNearbySlice(canvasX, canvasY);
  3168. if (options.grid.autoHighlight) {
  3169. // clear auto-highlights
  3170. for (var i = 0; i < highlights.length; ++i) {
  3171. var h = highlights[i];
  3172. if (h.auto == eventname && !(item && h.series == item.series)) {
  3173. unhighlight(h.series);
  3174. }
  3175. }
  3176. }
  3177. // highlight the slice
  3178. if (item) {
  3179. highlight(item.series, eventname);
  3180. }
  3181. // trigger any hover bind events
  3182. var pos = { pageX: e.pageX, pageY: e.pageY };
  3183. target.trigger(eventname, [pos, item]);
  3184. }
  3185. function highlight(s, auto) {
  3186. //if (typeof s == "number") {
  3187. // s = series[s];
  3188. //}
  3189. var i = indexOfHighlight(s);
  3190. if (i == -1) {
  3191. highlights.push({ series: s, auto: auto });
  3192. plot.triggerRedrawOverlay();
  3193. } else if (!auto) {
  3194. highlights[i].auto = false;
  3195. }
  3196. }
  3197. function unhighlight(s) {
  3198. if (s == null) {
  3199. highlights = [];
  3200. plot.triggerRedrawOverlay();
  3201. }
  3202. //if (typeof s == "number") {
  3203. // s = series[s];
  3204. //}
  3205. var i = indexOfHighlight(s);
  3206. if (i != -1) {
  3207. highlights.splice(i, 1);
  3208. plot.triggerRedrawOverlay();
  3209. }
  3210. }
  3211. function indexOfHighlight(s) {
  3212. for (var i = 0; i < highlights.length; ++i) {
  3213. var h = highlights[i];
  3214. if (h.series == s)
  3215. return i;
  3216. }
  3217. return -1;
  3218. }
  3219. function drawOverlay(plot, octx) {
  3220. var options = plot.getOptions();
  3221. var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
  3222. octx.save();
  3223. octx.translate(centerLeft, centerTop);
  3224. octx.scale(1, options.series.pie.tilt);
  3225. for (var i = 0; i < highlights.length; ++i) {
  3226. drawHighlight(highlights[i].series);
  3227. }
  3228. drawDonutHole(octx);
  3229. octx.restore();
  3230. function drawHighlight(series) {
  3231. if (series.angle <= 0 || isNaN(series.angle)) {
  3232. return;
  3233. }
  3234. //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
  3235. octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
  3236. octx.beginPath();
  3237. if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
  3238. octx.moveTo(0, 0); // Center of the pie
  3239. }
  3240. octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
  3241. octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
  3242. octx.closePath();
  3243. octx.fill();
  3244. }
  3245. }
  3246. } // end init (plugin body)
  3247. // define pie specific options and their default values
  3248. var options = {
  3249. series: {
  3250. pie: {
  3251. show: false,
  3252. radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
  3253. innerRadius: 0, /* for donut */
  3254. startAngle: 3/2,
  3255. tilt: 1,
  3256. shadow: {
  3257. left: 5, // shadow left offset
  3258. top: 15, // shadow top offset
  3259. alpha: 0.02 // shadow alpha
  3260. },
  3261. offset: {
  3262. top: 0,
  3263. left: "auto"
  3264. },
  3265. stroke: {
  3266. color: "#fff",
  3267. width: 1
  3268. },
  3269. label: {
  3270. show: "auto",
  3271. formatter: function(label, slice) {
  3272. return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
  3273. }, // formatter function
  3274. radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
  3275. background: {
  3276. color: null,
  3277. opacity: 0
  3278. },
  3279. threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
  3280. },
  3281. combine: {
  3282. threshold: -1, // percentage at which to combine little slices into one larger slice
  3283. color: null, // color to give the new slice (auto-generated if null)
  3284. label: "Other" // label to give the new slice
  3285. },
  3286. highlight: {
  3287. //color: "#fff", // will add this functionality once parseColor is available
  3288. opacity: 0.5
  3289. }
  3290. }
  3291. }
  3292. };
  3293. $.plot.plugins.push({
  3294. init: init,
  3295. options: options,
  3296. name: "pie",
  3297. version: "1.1"
  3298. });
  3299. })(jQuery);
  3300. </script>
  3301. <script type="application/javascript">/* Flot plugin for automatically redrawing plots as the placeholder resizes.
  3302. Copyright (c) 2007-2014 IOLA and Ole Laursen.
  3303. Licensed under the MIT license.
  3304. It works by listening for changes on the placeholder div (through the jQuery
  3305. resize event plugin) - if the size changes, it will redraw the plot.
  3306. There are no options. If you need to disable the plugin for some plots, you
  3307. can just fix the size of their placeholders.
  3308. */
  3309. /* Inline dependency:
  3310. * jQuery resize event - v1.1 - 3/14/2010
  3311. * http://benalman.com/projects/jquery-resize-plugin/
  3312. *
  3313. * Copyright (c) 2010 "Cowboy" Ben Alman
  3314. * Dual licensed under the MIT and GPL licenses.
  3315. * http://benalman.com/about/license/
  3316. */
  3317. (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
  3318. (function ($) {
  3319. var options = { }; // no options
  3320. function init(plot) {
  3321. function onResize() {
  3322. var placeholder = plot.getPlaceholder();
  3323. // somebody might have hidden us and we can't plot
  3324. // when we don't have the dimensions
  3325. if (placeholder.width() == 0 || placeholder.height() == 0)
  3326. return;
  3327. plot.resize();
  3328. plot.setupGrid();
  3329. plot.draw();
  3330. }
  3331. function bindEvents(plot, eventHolder) {
  3332. plot.getPlaceholder().resize(onResize);
  3333. }
  3334. function shutdown(plot, eventHolder) {
  3335. plot.getPlaceholder().unbind("resize", onResize);
  3336. }
  3337. plot.hooks.bindEvents.push(bindEvents);
  3338. plot.hooks.shutdown.push(shutdown);
  3339. }
  3340. $.plot.plugins.push({
  3341. init: init,
  3342. options: options,
  3343. name: 'resize',
  3344. version: '1.0'
  3345. });
  3346. })(jQuery);
  3347. </script>
  3348. <script type="application/javascript">
  3349. $(document).ready(function() {
  3350. var row = 0;
  3351. var MINOR_AUTHOR_PERCENTAGE = 1.00;
  3352. var isReversed = false;
  3353. var colorRows = function() {
  3354. $(this).removeClass("odd");
  3355. if (row++ % 2 == 1) {
  3356. $(this).addClass("odd");
  3357. }
  3358. if(this == $(this).parent().find("tr:visible").get(-1)) {
  3359. row = 0;
  3360. }
  3361. }
  3362. // Fix header and set it to the right width.
  3363. var remainingHeaderWidth = ($("div.logo").width() - 4) - ($("div.logo img").innerWidth() + 48)
  3364. $("div.logo p").css("width", remainingHeaderWidth);
  3365. var filterResponsibilities = function() {
  3366. $("table#blame tbody tr td:last-child").filter(function() {
  3367. return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
  3368. }).parent().find("td:first-child").each(function() {
  3369. $("div#responsibilities div h3:contains(\"" + $(this).text() + "\")").parent().hide();
  3370. });
  3371. }
  3372. var filterTimeLine = function() {
  3373. $("div#timeline table.git tbody tr").filter(function() {
  3374. return $(this).find("td:has(div)").length == 0;
  3375. }).hide();
  3376. }
  3377. $("table#changes tbody tr td:last-child").filter(function() {
  3378. return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
  3379. }).parent().hide();
  3380. $("table#blame tbody tr td:last-child").filter(function() {
  3381. return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
  3382. }).parent().hide();
  3383. $("table.git tbody tr:visible").each(colorRows);
  3384. $("table#changes, table#blame").tablesorter({
  3385. sortList: [[0,0]],
  3386. headers: {
  3387. 0: { sorter: "text" }
  3388. }
  3389. }).bind("sortEnd", function() {
  3390. $(this).find("tbody tr:visible").each(colorRows);
  3391. });
  3392. $("table#changes thead tr th, table#blame thead tr th").click(function() {
  3393. $(this).parent().find("th strong").remove();
  3394. var parentIndex = $(this).index();
  3395. if (this.isReversed) {
  3396. $(this).append("<strong> &and;</strong>");
  3397. } else {
  3398. $(this).append("<strong> &or;</strong>");
  3399. }
  3400. this.isReversed = !this.isReversed;
  3401. });
  3402. $("table#changes thead tr th:first-child, table#blame thead tr th:first-child").each(function() {
  3403. this.isReversed = true;
  3404. $(this).append("<strong> &or;</strong>");
  3405. });
  3406. $("table.git tfoot tr td:first-child").filter(function() {
  3407. this.hiddenCount = $(this).parent().parent().parent().find("tbody tr:hidden").length;
  3408. return this.hiddenCount > 0;
  3409. }).each(function() {
  3410. $(this).addClass("hoverable");
  3411. this.innerHTML = "Show minor authors (" + this.hiddenCount + ") &or;";
  3412. }).click(function() {
  3413. this.clicked = !this.clicked;
  3414. if (this.clicked) {
  3415. this.innerHTML = "Hide minor authors (" + this.hiddenCount + ") &and;";
  3416. $(this).parent().parent().parent().find("tbody tr").show().each(colorRows);
  3417. } else {
  3418. this.innerHTML = "Show minor authors (" + this.hiddenCount + ") &or;";
  3419. $(this).parent().parent().parent().find("tbody tr td:last-child").filter(function() {
  3420. return parseFloat(this.innerHTML) < MINOR_AUTHOR_PERCENTAGE;
  3421. }).parent().hide();
  3422. $("table.git tbody tr:visible").each(colorRows);
  3423. }
  3424. });
  3425. filterResponsibilities();
  3426. var hiddenResponsibilitiesCount = $("div#responsibilities div h3:hidden").length;
  3427. if (hiddenResponsibilitiesCount > 0) {
  3428. $("div#responsibilities div h3:visible").each(colorRows);
  3429. $("div#responsibilities").prepend("<div class=\"button\">Show minor authors (" + hiddenResponsibilitiesCount + ") &or;</div>");
  3430. $("div#responsibilities div.button").click(function() {
  3431. this.clicked = !this.clicked;
  3432. if (this.clicked) {
  3433. this.innerHTML = "Hide minor authors (" + hiddenResponsibilitiesCount + ") &and;";
  3434. $("div#responsibilities div").show();
  3435. } else {
  3436. this.innerHTML = "Show minor authors (" + hiddenResponsibilitiesCount + ") &or;";
  3437. filterResponsibilities();
  3438. }
  3439. });
  3440. }
  3441. filterTimeLine();
  3442. var hiddenTimelineCount = $("div#timeline table.git tbody tr:hidden").length;
  3443. if (hiddenTimelineCount > 0) {
  3444. $("div#timeline table.git tbody tr:visible").each(colorRows);
  3445. $("div#timeline").prepend("<div class=\"button\">Show rows with minor work (" + hiddenTimelineCount + ") &or;</div>");
  3446. $("div#timeline div.button").click(function() {
  3447. this.clicked = !this.clicked;
  3448. if (this.clicked) {
  3449. this.innerHTML = "Hide rows with minor work (" + hiddenTimelineCount + ") &and;";
  3450. $("div#timeline table.git tbody tr").show().each(colorRows);
  3451. } else {
  3452. this.innerHTML = "Show rows with minor work (" + hiddenTimelineCount + ") &or;";
  3453. filterTimeLine();
  3454. $("div#timeline table.git tbody tr:visible").each(colorRows);
  3455. }
  3456. });
  3457. }
  3458. $("#blame_chart, #changes_chart").bind("plothover", function(event, pos, obj) {
  3459. if (obj) {
  3460. var selection = "table tbody tr td:contains(\"" + obj.series.label + "\")";
  3461. var element = $(this).parent().find(selection);
  3462. if (element) {
  3463. if (this.hoveredElement && this.hoveredElement.html() != element.parent().html()) {
  3464. this.hoveredElement.removeClass("piehover");
  3465. }
  3466. element.parent().addClass("piehover");
  3467. this.hoveredElement = element.parent();
  3468. }
  3469. } else if (this.hoveredElement) {
  3470. this.hoveredElement.removeClass("piehover");
  3471. }
  3472. });
  3473. // Make sure the two pie charts use the same colors.
  3474. var author_colors = {};
  3475. $.each(changes_plot.getData(), function(i, v) {
  3476. author_colors[v["label"]] = v["color"];
  3477. });
  3478. $.each(blame_plot.getData(), function(i, v) {
  3479. if (author_colors[v["label"]] != undefined) {
  3480. v["color"] = author_colors[v["label"]];
  3481. }
  3482. });
  3483. blame_plot.setupGrid();
  3484. blame_plot.draw();
  3485. // Color in metrics levels.
  3486. $("div#metrics div div").each(function() {
  3487. var rgb = $(this).css("background-color").match(/\d+/g);
  3488. rgb[0] = parseInt(rgb[0]);
  3489. rgb[1] = parseInt(rgb[1]);
  3490. rgb[2] = parseInt(rgb[2]);
  3491. if ($(this).hasClass("minimal")) {
  3492. rgb[0] -= 10;
  3493. rgb[1] += 10;
  3494. rgb[2] -= 10;
  3495. } else if ($(this).hasClass("minor")) {
  3496. rgb[1] += 10;
  3497. } else if ($(this).hasClass("medium")) {
  3498. rgb[0] += 10;
  3499. rgb[1] += 10;
  3500. } else if ($(this).hasClass("bad")) {
  3501. rgb[0] += 10;
  3502. rgb[1] -= 10;
  3503. rgb[2] -= 10;
  3504. } else if ($(this).hasClass("severe")) {
  3505. rgb[0] += 20;
  3506. rgb[1] -= 20;
  3507. rgb[2] -= 20;
  3508. }
  3509. $(this).css("background-color", "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")");
  3510. });
  3511. });
  3512. </script>
  3513. <style type="text/css">
  3514. body {
  3515. background: -webkit-linear-gradient(left, #8f8a9a, #dad2d7, #8f8a9a);
  3516. background: -moz-linear-gradient(left, #8f8a9a, #dad2d7, #8f8a9a);
  3517. }
  3518. html, body {
  3519. margin: 0;
  3520. font-family: "Arial";
  3521. }
  3522. body > div {
  3523. margin: 0 auto;
  3524. width: 58em;
  3525. }
  3526. div.box {
  3527. border: 4px solid #ddd;
  3528. background-color: #eee;
  3529. margin: 0.75em;
  3530. padding: 5px;
  3531. font-size: small;
  3532. border-radius: 15px;
  3533. -moz-border-radius: 15px;
  3534. box-shadow: 1px 1px 3px #666;
  3535. -moz-box-shadow: 1px 1px 3px #666;
  3536. }
  3537. div.logo p {
  3538. width: 60em;
  3539. display:inline-block;
  3540. vertical-align:middle;
  3541. }
  3542. div.logo img {
  3543. vertical-align:middle;
  3544. padding: 2px 10px 2px 2px;
  3545. }
  3546. body > div {
  3547. display: block-inline;
  3548. }
  3549. body > div > div > div {
  3550. position: relative;
  3551. width: 100%;
  3552. min-height: 140px;
  3553. }
  3554. table.git {
  3555. font-size: small;
  3556. width: 65%;
  3557. padding-right: 5px;
  3558. }
  3559. table.full {
  3560. width: 100%;
  3561. }
  3562. table.git th, table.git tfoot tr td {
  3563. padding: 0.3em;
  3564. background-color: #ddcece;
  3565. border-radius: 8px 8px 0px 0px;
  3566. -moz-border-radius: 8px 8px 0px 0px;
  3567. }
  3568. table#changes thead tr th, table#blame thead tr th, table.git tfoot tr td {
  3569. border: 1px solid #eee;
  3570. text-align: center;
  3571. }
  3572. table.git tfoot tr td {
  3573. border-radius: 0px 0px 8px 8px;
  3574. -moz-border-radius: 0px 0px 8px 8px;
  3575. text-align: center;
  3576. }
  3577. table.git td, table.git th, table#timeline td, table#timeline th {
  3578. padding: 0.35em;
  3579. height: 2em;
  3580. }
  3581. table.git td div.insert {
  3582. background-color: #7a7;
  3583. }
  3584. table.git td div.remove {
  3585. background-color: #c66;
  3586. }
  3587. table.git td div.insert, table.git td div.remove {
  3588. height: 100%;
  3589. float: left;
  3590. }
  3591. table.git tr.odd {
  3592. background-color: #dbdbdb;
  3593. }
  3594. table.git tr.piehover {
  3595. background-color: #dddece;
  3596. }
  3597. div.chart {
  3598. position: absolute;
  3599. top: 5px;
  3600. bottom: 5px;
  3601. right: 0px;
  3602. width: 35%;
  3603. min-height: 100px;
  3604. max-height: 210px;
  3605. font-size: x-small;
  3606. }
  3607. p.error {
  3608. color: #700;
  3609. }
  3610. table#changes thead tr th:hover, table#blame thead tr th:hover,
  3611. table#changes tfoot tr td.hoverable:hover, table#blame tfoot tr td.hoverable:hover,
  3612. div.button:hover, div#responsibilities div.button:hover {
  3613. background-color: #eddede;
  3614. border: 1px solid #bbb;
  3615. cursor: hand;
  3616. }
  3617. div#responsibilities div, div#responsibilities div div, div#metrics div, div#metrics div div {
  3618. min-height: 0px;
  3619. padding: 0.5em 0.2em;
  3620. width: auto;
  3621. }
  3622. div#metrics div {
  3623. background-color: #eee;
  3624. }
  3625. div#responsibilities div.odd, div#metrics div.odd {
  3626. background-color: #dbdbdb;
  3627. }
  3628. div#responsibilities p {
  3629. margin-bottom: 0px;
  3630. }
  3631. td img, h3 img {
  3632. border-radius: 3px 3px 3px 3px;
  3633. -moz-border-radius: 3px 3px 3px 3px;
  3634. vertical-align: middle;
  3635. margin-right: 0.4em;
  3636. opacity: 0.85;
  3637. }
  3638. td img {
  3639. width: 20px;
  3640. height: 20px;
  3641. }
  3642. h3 img {
  3643. width: 32px;
  3644. height: 32px;
  3645. }
  3646. h3, h4 {
  3647. border-radius: 8px 8px 8px 8px;
  3648. -moz-border-radius: 8px 8px 8px 8px;
  3649. background-color: #ddcece;
  3650. margin-bottom: 0.2em;
  3651. margin-top: 0.6em;
  3652. }
  3653. h4 {
  3654. margin-top: 0.2em;
  3655. padding: 0.5em;
  3656. }
  3657. div.button, div#responsibilities div.button {
  3658. border-radius: 8px 8px 8px 8px;
  3659. -moz-border-radius: 8px 8px 8px 8px;
  3660. border: 1px solid #eee;
  3661. float: right;
  3662. width: auto;
  3663. padding: 0.5em;
  3664. background-color: #ddcece;
  3665. min-height: 0;
  3666. }
  3667. </style>
  3668. </head>
  3669. <body>
  3670. <div><div class="box logo">
  3671. <a href="https://github.com/ejwa/gitinspector"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABBCAYAAABGmyOTAAAACXBIWXMAABcRAAAXEQHKJvM/AAAWnElEQVR4nO1caXRcxZX+blW9XrVZsmVsg8E2m8GAwBgHs4NDIBgDSQwJSxKGQIYEOMxhzuRkGUaTbQJJhmGADCEJmQFCAJMAtiFwwhZ2g1ds2bIt27Isa1erW72+re78eGqpu9WtljfwnDPfOT6W3ruvXtX3bt26de8tEcYG/R/8Off3wusugAwAGwDjACDvBcygljsu9a3Y3RnsiNoBCSOgglKGSMqKMKmggiIWMmz4JUkoYpKBAElyoZSQighKSJKSIUFQUkgpCEoI73clpIRmRYKEIChBkARIhlACUFJAMEERQRJDCgjFBCkIkogUCBIMSYASAgLw+iMIkokVAZKIBIEUMyQRpCSuhONWRuOZda/u6Prvu1as2wggCSAGwDlgBHJjo3iz6c3QpGjv5azkaZZm5WomImIg/3MxNA/9wCPXKP+LEhg65z5x4RdnznleUP7zXKAhPFqbxg1i1u7kmvCkuoqrNvXEfn/VUx8sBdAHIIL9JJEAj7zI2tenuRR/2NU4ymWIbL+zA8njaugaeaTxiKgnqEdaZzDl3ydwTmOsARCBeeibgMAo/BgHCEnNHTR9wvyXd/Z+/65Xmj4C0AVgoFBu2+3XVcVam53Tl69OlWtTMIOAJsUi+QAzTXeZ5RjyYw5slI4VgsrYnXL39xNhQVOd7sF1CyZV3gjAB6ACgCqUy/S1fVu66QffaDx/1L1CCDxyi+praj2TwDM0s8i5l6Nyo8dF5bSkPBmeFh9k0gpRZeu6sKSTagwDAAQAo1BGJ+N+x9apCxrfLDu9VXtyQIVNZ7ZLBE8by06fIvdHdE+Pvolyujk8fT8xUPSCI+tqnmvpigIQhXe1qxMMntX01YXXIhQasb0MAlzE2/qWfeYvKwcBQPkHIUlQoCgt5Zf6vPtUyBQX+Rg56qxRbGU4OPYvvw+MyqAYpXlZaKV2k2Wem+na/eWRR7zZqUFBbfMqAB6BSdiqZpyvLdOnsZfJT9n+5aKsVmhargP+D3VNDQL+alBlJZndbcjEYjBjJqZMMdqzssqPoIKA52IWeUd5+1dkeh6i9m+8GFp9dzb9w5JaZdvzHTtVF/T5t4RCev2JL261cmWVNJISRDzkabE3z0tizAGXtX9FvsYnb//Ghx3/dMNN0Q2rLzRd1wfA1cCXHFD3uivP/F3D8+9/mJUTFvuLLtV7a/8EQUsirQS5isgVIJfGaKM42aXtHwFMAAuCzv7LXivTz73Gumsv+NzA+lWfjWac/raY+VLnoPXnhOm8roAArMFbXpp/dFVWVkkHhX5fsQ6V7KTJcBLEnaZEjyFIKyFl0HUrK1wc7gIy62mPaSLHmMoutC8pBWyDgkTCEVLCYI4F0o5WzIb3eHkTvDdwUonTEqaVfqt94AeNLZHB7PWPLm+4QpuZm1yh5wF4DQCUhFZakyjG0Vj2z9Ka9mhn006mFZ2J5IY3Wvt3d9pB82dnHn5YlU8elwiJ86rSelLYdY93mIVmiOyWBBiP/WMaYCdgBv3mLlM/1bw7tqpx5Y7Nv1k4p37O9OqTqDp4SzJpqpqEmWQXFVlN3EsiCUXklW2poCHdJadOyTS2RIav15w0LxNf8y7S0MaILLRSGNc8GBYxXQebbef1R3dEfvvc9u7dAFIAHGCQP/9893YA25v/7qy3rWrjmyKS6gmm7XMcBmuwyH1Pvv3Ln779wq3arcXLNy9v+nWfafbDcxvsm1/d2A9gM4BntnzrwpvjhvxWZTS1Cy4m8tj2uxiKEgjCRgU02Fbgh5uvP/9lPTjYrmYdc3ps45qFFlMUcEZsoJCjpnAZ+8e0k3nVPZs6f//c9u498MgbFR46/tF34xV+8yF9WE3GUvIDQdBZrStu/0YQZSe4R9PrV63Y8HCfaUYARIfekYfjfvX6b0LVlb8zD6uVErCGGNlvm3jK6etfyGheqa3UkZmerjusTPL+ZNO6L9pWRq3sGHjs6vfbh9VSCDePwDHtH4G4X7t9r/cnlr2zJ9qDkdhaURxx3/tpkPqTWV/ZL4EkgXjU1C343QWrdFDhrre3/x7exxlEoZOVg5n3v/KgL+SrSQV8qwSV/TbjAjVCn/XatntbouZPuyx3mfAHX/w4Yv65OWPcefEZJ63JlVWuEEYxl7yY/QOALsfZ/cimji0oQ14Ws+57acOuf/z8Nxw5uJpcfS68OB2Xcl9SYBVh9WpLLJWCp1VWUcEcyIDxX2pq7VlyZ7d2meX+Liobv3r+N6z+/lNOe3HD7QA2ZK+/ccmc663ujiuWnz3zrsvf2dEGAIL06ClcBNmQlogI2dafsFx4WlFSM3JhBHw7nIpgpwCV0JAR++cEVKa5P74RHnFlPxAAQNOHRtA43nXRNy75cs2ZVhVpnlB4vcavdrBlhTS5J2WvedHiHIxl/zQBSabE3naIfMpGyHAQAzyyuKR2MAmOZpw4cuKOZRHwR4Rt11rQrQQ6bG/7l0XzbYunCsMIOH39IW0ofPzFeTNjsRjaYhnMvfjSWnN708VOKsOwOZp9RrlgJcE0tAsZNZ7sDwRiwUw1fpHdOouhf2W1UAg52U45O41C4oq7Mua06mAdvDhd2XgcPKHDoFQXezE+2teFxOro+K6bSs8A2GXWDlvmA2FBOLYmQPGP/uYIYk47ekdVQKwefreAVKD8oZSyfwTSFYQJ86dPrFzZ1heDZwfHJJAbl/j6pTjcjiV79VCku5T9YzAJ8NTqkL8G3mI9rils+MR5aZtXGkKc7Xh5gn2zf37fk/5w+GhIH6QUcC0bg/EkOns68V7nIAWF0TsnUP/a1U3bhu2yIO2Us4F5o610dODmk6edCSAEwI8i8bRc9Pn8l2Q0NgVZL9BgoUdN32zOhanTNifuttyXrn1uzQvwXJdkuTFz4xIfCXlbZ3P7Lg0O7I8bc/If33v/hMf/9rgIVO5wLdPXvVv/8TPPr3z8qvfaHv/5zuhj846YEJ81y/jeG+cfNRzAEhpyZJrkx++KujRTQcceEQ7M+c55x88BUA0gjBIkRn9+wwzho680v9+8S4JmlPIxGEz95E7sD/iXL35h3cMA+uERWNY8DFQE/j2p8XJNLLFwHxzporAiXRek+nvPrp/Umzeu2ppgiEzzDBPi9Ow1QV6qcFgLijWYG74ySIRmpDMnXDa99pr7L2m4AEAdgCoU2Kv+X16/wCb6yc71be8fC/52Qbpg2P4xmHrhTuzxB5YvfmH9WOQpeOF3AoAPGi+t6r3nuscsosN3vfpxxCfEUV57+0+inRwMJJIZ68SlTXkuVF1dfRKsuSfuDK/QisB5Ay9l/3IkOMhUNXUgNRM1/s/96KLZsX9+bfMqeG6HAwBdP/rSIjh0Uf+arU2TU5lrdW76NMf+MYO6HLOuz+dfvvjZVUXJG7j32oa0ad/S2jO4aUt3dMesuqrph9VXn1JbEbqsczD9VPfbG1qODgc+7/X9wGigINFb5VfHrbt6/gUNz6x8AwCaGpf4rE17LoEgtz6ATVlZpcFKAjm5yHy2Sr3EB4RkLFGnNUuMrMgeNFdafZGtNWnzOqvEtGIGRRRPjPhCyxY/t6YoeYn7vt5gs366fU/0IbKdY06dPul8khItXbGmxLvND8wxaMHR4UD9UEcPWDTG1v6nFWdOdWOx2z685MQvCnac5KotYWlZh9ma113ybltTVlYRQ+rRgxxzSzfk5jMBkCSyxA23QSDhgkTu6pSb/2CAetmu61OhZYv+VFzzEvd9vcGV4un169t+fHhn/zVK0ASXOU1E6gRgIQcUlero/uKMlz7qWn72zLuh9Y1VfmNqld+o7xqId/eknJds4f5PrqwiCAUSVMpej5m+ZM6SVPABtBiObBdEuRmgLteq6zN8yxaXIW/t+tZh8gBAEIWG2jjoGNqq/Ws5OaUJMqsp7IXfx5gKBfkPAkHz6LQgSBAo7zoRWGumiI/rIiI8NnmCnl29fufd0zsj12bJO5BTdDzY9r2bP5feufliNjMTg4dNdY791bKbmr4y70SI8Dmweh/NLjCKwEpDF/NDSro0NGIyScvhmFrOAEloaJGXpWdQN9t1ERlatqjEgjFM3setnyp5zXdedWFizTs3aa0tCIlMV0caANhXc7Ld1b6oN2GuArAKAEThKoy9KN8gIsqxgSPXoQWGNJAZ5ALUo626Xmksu+wQJw8AMq1tZ1muznDdlDtVRWiTZq+bDnMzg9WA68zMygpiWXInUq58gxlU3AaSgGDSADRAAwZq+0KhZYuXF/fzEg/d3KBHyLtBCZrA3hblEycPAFzHlKx1On4cokIogMnrRyZxDJhd0xkJqAjQUDRmdPlZAYreJxLDzvfwYLWjpZOxCQTqtNK1XUKV1ryHbm7QjvPs2hHyqj4t4rKQWm/yGXLKxKZMIwk5hYhE821X3mTHE5fFbbdvanVwZVZWMOvi8dTiP+faPzCYiMWowWqHKR1N+vqUmNhOYsVlpRaMIfJWfdx697RDhDwAOOWMTc+mLLyViUZnW5G+I5xEoj61dfPllm3LHXHriYWvNfdnZdWwBgIAEYPHiNUVjo5AECxQeFmy0Abd1ENYevUrmx9DCZunHfvZtR/vuvvIzoHr5SFCHuCF9IHmXzx17jGzbcc5Z0tPwj16QmWGbf3ijevaormyipgke/WjhdyUrWUhJkEsRw2aGJKUeGThs6sfhafBeeQl7//GXIf0k6vX7xwiD9WHCnm5WHDhRTLV3mafPLm70pgw0aZgSGJda56MYpDSDJ2zkhSr0ypKJkn4cvzAkYADy6Un/PqtbgAS3iYkjzyX9JOr1+z81yN7Dl3ytv795Xf0vv/WuWDtJ8DvJpMZLeiy9VecuvSUF9Y+m5UTGO3G5KKo/WOAoqTrM0JGXtna0QQgDi+4CgCY9NM/d2IkIJqvedBPrlp/aJPXdOuVlyZaW86LO3rP7njmQS3VrV3JzB+066bdTPrLH1w0Z3JWVtHwKjx2ICbX/kVcq6bDpRcX/aXplxhn7C56/9fmuqSf/Gjtjh/N6I4esuQBQLpzd0PGtG1m51+ufL81a/O2r108N8Fm8vaWgYF5AFYAXo200sTjtn8JxzWifrVu0V+a/gNezjaGcuT94mtz2eEnP1qz/cdHdQ9cdyiTBwBw7EpNlL7gzda8BUOEAhJgNa1+wrAfqIBRifWCgeWX78YUu2/2pf8AIA0v5D5mHTE/s0R2b04tbW/r+c7M3tjX6RBabUtDx/2CZq36wrzvGn4fAECQIDs+OJMY8Ql+48K1VzQsgFCxUQHVHBSzf0IEDPXDdzZugRdAzRR/NOe5q5e67d+7YlNd3+AcJanSPUTrAXOhlW+37ZpznVh0rgmA8j0UjUTiRABIuxxTDFLDBZYlkLV/LKDSmjvgBU8Z4zykIpRsE3XVGn2xfR7UJ4kznl/zBIAnxiPrTWEeSaWOVb7LDK18w5Uge5EXpmrHkFt84+nRXsAFlMUUCBLvdbJ/LGy7a8kJWvqmHBv6wxvUCL3zziUN6fjgl3QmY5CQr5z4+OuvZ2UVDdlA76hWaS3UAATDDfuNw2sMg6O2LeElstPlOiSVOiE1kHzbGGOXsy9ozzizYhm76qiqQEuVQLTf1hMlw67xyWj5p0tjsHXXVamB6MlO3Ylvb18yJRhv3X6XmU5WgWERcOurFx2/PrudE1w8+z/K/mX/z8RS3fcsnL0AQBBeSnNMUrp+eN1JwqdsMZg8UvPYOeS9hXa1YwiYCjATGhVticzMAduu2992hWlWadZ9Jy5tsgaVucROpyYO2vqphEUPEVA7mEqdOSybdaRLpTQL9yBB2x2YN7nylmPrQlXw8sKhUh3hZ5ZI6eef7emK/SZsyPnee/Z+BWaCyIDCGc5/14ywb/vsquDHIUEjZ9po/78Rs04Gpajbctc1l3IycU7cdNqFnrY0GJQ9RLBiGXc4rSkIpJwhzaA8V7r48YVaIae6aavvqWsW/NtxE6snAJgMoBJFkuuRNv/DDijubGydw4zAvgymX9O0LYPmSVuiyRO2xdKzNw1mGnYlrRkA0Ga6R2+MpU/LaB5uO245E5oGM6c1xTOnJSAm7ss7BemXJcFIbFh3u5tO1ZDG8gvefNMh22oghqjzGduGZTnHDyylHYUZz/qkU0uRuFx6VcMfr549dSa85PrwIPp/uuSE/l/c8GHGcsORNzc6E3xq3ljtl0LExeTdg4lpWmtUGbI/ZIi4rV2frbW3HmnNttaGzslIGILMkKDBIFGcHKesfS6GhhVNHxpVld+3qyYttatr7znvrZZlACBgbLa0XpFJhIeLLAsd6aFxjqDU8a3ahFnZS5w4fVr11Gc2d+yCVyeTAgDbwQ2w0sucVdvOCElMKmxzvIiYdp0A6elhX0uFkklbw7fZdk8e6xm/pNSMsH/7vrwvFyf/aXVzx29/nE60tsz9cGHkRg2XXFjQLpLV06SBJmSTSt4iQjx6FS5X5mkNJpR2XaDwQa2RiabYEKLG1lz2FGwxEADLcXw+IcwKJZP5dw4uOhpvCQ1s3/azPUufOEYxHEMwAAHNgsjgoOOmPgC86gTFXpFlwTwtd3xrnzjZKxStrvyENoD925pvtfp6Z6a1WMuMltrqaiDgh5sxoTMZVIN3ZWWLTeE87G/V9v4wrUC2qXUw7urKSiniI/yNZlIAriDS4P1fhq2BgfqUZUfOOa/lB150eqw+glSOC5MXTBjX8dWDiBqf7OtIu0e1Jcyjg0qmLWafy1plu5jbGT9gSYKTcXUoautaW8pwWCAWAg8Wb700JMEMGkqXIw/wprBkXcb+FSvFHYofHswTl5MDRrfNbMRttybpOCFFZBFBZ/vjam8XydCQQjoVSkYHLKd+RyJ9HADU+pQ6KuzfawKFEEliPXH1olOeE5RbLwACiaCVSN0+/7XmrcBQZQLnbbH2Kb150HB40NeOINoBIOaibsdg8hiDyAaAer/RVSFlPChFBgCOCvt3hqRIWloHlBBWnU/tU9W+q4w1GZ2ejnQauUm2oaUiIDUPO+5qrKrOA3BqZb/I7jadqXHHrfFJaTGDErYdlkROjU9GAKBCyWT+Cg3UB4yevezfqD6e+tyqvwL463gaUI5lRRH0CaSsUeVrn6b9AwDTdVXGcX1pxw0AgCRyJvnVnmpD7VdcjLWufKctGoWnIw4AcGOjWN/y1tdsx6GxKtsJ7HPao0+f886GAQBQtmmvw4RQvUhb7HjaWFiBVcalOXiYHvK3AWhLOm5ICXL8QpQ9tVQOjkDQst3kjljKgheKswFgxfJHApMnVFxNgF8wAMqfusPPawTJ4Lcx9PdmlFLG72y/fs8UtE6wPpJ5rHMWn6z9yyKsZNk/gDMeMJhiEvVN0fR/wouoJzEUz7x8dUdqxVlH3rspmvJ3xoqHOIVkUgC+esbU4b2wmn7vso5tt332juTE8AP+aHqnz7Srh6oE2Qs7D09dHikMB4ihlaGYUkwoCCRoBhzLhhxayA/GqfK9hSMoMEDu1IjDb9/4StNyeNnEeK7Mond3vTGetu7ZNfLHjhQAHPPgX5dt/dZn25Jh9yfJgDgOJG2wNgCG1iVqnB2XA4asMOKOC8+OjHw2rcHsfjNeG5KO4xzQGOC4kZ0sRHAc7Wqt9e40/fILyzc8AyCBMqdAx4ti5NA718yf35exjaRjKrcEgXABSGB520DL0qaOGEbODWP395dMi/X0TG0diAciSbPk32f5pPBBW6ztV03t/fD6Z2K8hxj/Hwcf/wvPBJZ+9MswgwAAADt0RVh0QXV0aG9yAEFkYW0gV2FsZGVuYmVyZywgYmFzZWQgb24gdGhlIEdpdCBMb2dvIGJ5IEphc29uIExvbmdbAluwAAAAJWlUWHRDb3B5cmlnaHQAAAAAAMKpIDIwMTUgQWRhbSBXYWxkZW5iZXJn6lGO2AAAAsJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkJz8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0nYWRvYmU6bnM6bWV0YS8nIHg6eG1wdGs9J0ltYWdlOjpFeGlmVG9vbCA5LjI3Jz4KPHJkZjpSREYgeG1sbnM6cmRmPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjJz4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOmNjPSdodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMnPgogIDxjYzpsaWNlbnNlPkNyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24gMy4wIFVucG9ydGVkIExpY2Vuc2UuIFRoaXMgbGljZW5zZSBsZXRzIG90aGVycyBkaXN0cmlidXRlLCByZW1peCwgdHdlYWssIGFuZCBidWlsZCB1cG9uIHlvdXIgd29yaywgZXZlbiBjb21tZXJjaWFsbHksIGFzIGxvbmcgYXMgdGhleSBjcmVkaXQgeW91IGZvciB0aGUgb3JpZ2luYWwgY3JlYXRpb24uIFRoaXMgaXMgdGhlIG1vc3QgYWNjb21tb2RhdGluZyBvZiB0aGUgQ0MgbGljZW5zZXMgb2ZmZXJlZC4gUmVjb21tZW5kZWQgZm9yIG1heGltdW0gZGlzc2VtaW5hdGlvbiBhbmQgdXNlIG9mIGxpY2Vuc2VkIG1hdGVyaWFscy48L2NjOmxpY2Vuc2U+CiA8L3JkZjpEZXNjcmlwdGlvbj4KPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0ncic/PtC9Q+UAAAAASUVORK5CYII=" /></a>
  3672. <p>Statistical information for the repository 'libreflix' was gathered on 2018/02/25.<br>The output has been generated by <a href="https://github.com/ejwa/gitinspector">gitinspector</a> 0.5.0dev. The statistical analysis tool for git repositories.</p>
  3673. </div></div>
  3674. <div><div class="box"><p>The following historical commit information, by author, was found.</p><div><table id="changes" class="git"><thead><tr> <th>Author</th> <th>Commits</th> <th>Insertions</th> <th>Deletions</th> <th>% of changes</th></tr></thead><tbody><tr ><td><img src="https://www.gravatar.com/avatar/df354948385c9e431161d4f3196bd099?default=identicon&size=20"/>Guilmour</td><td>3</td><td>3</td><td>3</td><td>0.01</td></tr><tr class="odd"><td><img src="https://www.gravatar.com/avatar/df354948385c9e431161d4f3196bd099?default=identicon&size=20"/>Guilmour Rossi</td><td>102</td><td>52156</td><td>18141</td><td>99.00</td></tr><tr ><td><img src="https://www.gravatar.com/avatar/b2be9029d6abe24857ac6d48e5ce1833?default=identicon&size=20"/>Julio Lira</td><td>7</td><td>29</td><td>13</td><td>0.06</td></tr><tr class="odd"><td><img src="https://www.gravatar.com/avatar/be138933daf832aec3fe27868b78b96c?default=identicon&size=20"/>jotarios</td><td>3</td><td>504</td><td>81</td><td>0.82</td></tr><tr ><td><img src="https://www.gravatar.com/avatar/016902e45ab8b7238990dca4576a9a20?default=identicon&size=20"/>n2omatt</td><td>3</td><td>66</td><td>11</td><td>0.11</td></tr><tfoot><tr> <td colspan="5">&nbsp;</td> </tr></tfoot></tbody></table><div class="chart" id="changes_chart"></div></div><script type="text/javascript"> changes_plot = $.plot($("#changes_chart"), [{label: "Guilmour", data: 0.01}, {label: "Guilmour Rossi", data: 99.00}, {label: "Julio Lira", data: 0.06}, {label: "jotarios", data: 0.82}, {label: "n2omatt", data: 0.11}], { series: { pie: { innerRadius: 0.4, show: true, combine: { threshold: 0.01, label: "Minor Authors" } } }, grid: { hoverable: true } });</script></div></div>
  3675. <div><div class="box"><p>Below are the number of rows from each author that have survived and are still intact in the current revision.</p><div><table id="blame" class="git"><thead><tr> <th>Author</th> <th>Rows</th> <th>Stability</th> <th>Age</th> <th>% in comments</th> </tr></thead><tbody><tr ><td><img src="https://www.gravatar.com/avatar/df354948385c9e431161d4f3196bd099?default=identicon&size=20"/>Guilmour Rossi</td><td>33898</td><td>65.0</td><td>6.5</td><td>5.90</td><td style="display: none">98.29</td></tr><tr class="odd"><td><img src="https://www.gravatar.com/avatar/b2be9029d6abe24857ac6d48e5ce1833?default=identicon&size=20"/>Julio Lira</td><td>22</td><td>75.9</td><td>0.5</td><td>0.00</td><td style="display: none">0.06</td></tr><tr ><td><img src="https://www.gravatar.com/avatar/be138933daf832aec3fe27868b78b96c?default=identicon&size=20"/>jotarios</td><td>502</td><td>99.6</td><td>0.6</td><td>41.04</td><td style="display: none">1.46</td></tr><tr class="odd"><td><img src="https://www.gravatar.com/avatar/016902e45ab8b7238990dca4576a9a20?default=identicon&size=20"/>n2omatt</td><td>66</td><td>100.0</td><td>2.9</td><td>37.88</td><td style="display: none">0.19</td></tr><tfoot><tr> <td colspan="5">&nbsp;</td> </tr></tfoot></tbody></table><div class="chart" id="blame_chart"></div></div><script type="text/javascript"> blame_plot = $.plot($("#blame_chart"), [{label: "Guilmour Rossi", data: 98.29}, {label: "Julio Lira", data: 0.06}, {label: "jotarios", data: 1.46}, {label: "n2omatt", data: 0.19}], { series: { pie: { innerRadius: 0.4, show: true, combine: { threshold: 0.01, label: "Minor Authors" } } }, grid: { hoverable: true } });</script></div></div>
  3676. </body>
  3677. </html>