12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258 |
- ;(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- define(['moment'], factory);
- } else if (typeof exports === 'object') {
- module.exports = factory.call(root, require('moment'));
- } else {
- root.Chart = factory.call(root, root.moment);
- }
- }(this || window, function (moment) {
- !function e(r, t, n) {
- function a(i, u) {
- if (!t[i]) {
- if (!r[i]) {
- var l = "function" == typeof require && require;
- if (!u && l) return l(i, !0);
- if (s) return s(i, !0);
- var h = new Error("Cannot find module '" + i + "'");
- throw h.code = "MODULE_NOT_FOUND", h
- }
- var o = t[i] = {exports: {}};
- r[i][0].call(o.exports, function (e) {
- var t = r[i][1][e];
- return a(t ? t : e)
- }, o, o.exports, e, r, t, n)
- }
- return t[i].exports
- }
- for (var s = "function" == typeof require && require, i = 0; i < n.length; i++) a(n[i]);
- return a
- }({
- 1: [function (e, r, t) {
- !function () {
- var t = e("color-convert"), n = e("color-string"), a = function (e) {
- if (e instanceof a) return e;
- if (!(this instanceof a)) return new a(e);
- if (this.values = {
- rgb: [0, 0, 0],
- hsl: [0, 0, 0],
- hsv: [0, 0, 0],
- hwb: [0, 0, 0],
- cmyk: [0, 0, 0, 0],
- alpha: 1
- }, "string" == typeof e) {
- var r = n.getRgba(e);
- if (r) this.setValues("rgb", r); else if (r = n.getHsla(e)) this.setValues("hsl", r); else {
- if (!(r = n.getHwb(e))) throw new Error('Unable to parse color from string "' + e + '"');
- this.setValues("hwb", r)
- }
- } else if ("object" == typeof e) {
- var r = e;
- if (void 0 !== r.r || void 0 !== r.red) this.setValues("rgb", r); else if (void 0 !== r.l || void 0 !== r.lightness) this.setValues("hsl", r); else if (void 0 !== r.v || void 0 !== r.value) this.setValues("hsv", r); else if (void 0 !== r.w || void 0 !== r.whiteness) this.setValues("hwb", r); else {
- if (void 0 === r.c && void 0 === r.cyan) throw new Error("Unable to parse color from object " + JSON.stringify(e));
- this.setValues("cmyk", r)
- }
- }
- };
- a.prototype = {
- rgb: function (e) {
- return this.setSpace("rgb", arguments)
- }, hsl: function (e) {
- return this.setSpace("hsl", arguments)
- }, hsv: function (e) {
- return this.setSpace("hsv", arguments)
- }, hwb: function (e) {
- return this.setSpace("hwb", arguments)
- }, cmyk: function (e) {
- return this.setSpace("cmyk", arguments)
- }, rgbArray: function () {
- return this.values.rgb
- }, hslArray: function () {
- return this.values.hsl
- }, hsvArray: function () {
- return this.values.hsv
- }, hwbArray: function () {
- return 1 !== this.values.alpha ? this.values.hwb.concat([this.values.alpha]) : this.values.hwb
- }, cmykArray: function () {
- return this.values.cmyk
- }, rgbaArray: function () {
- var e = this.values.rgb;
- return e.concat([this.values.alpha])
- }, hslaArray: function () {
- var e = this.values.hsl;
- return e.concat([this.values.alpha])
- }, alpha: function (e) {
- return void 0 === e ? this.values.alpha : (this.setValues("alpha", e), this)
- }, red: function (e) {
- return this.setChannel("rgb", 0, e)
- }, green: function (e) {
- return this.setChannel("rgb", 1, e)
- }, blue: function (e) {
- return this.setChannel("rgb", 2, e)
- }, hue: function (e) {
- return this.setChannel("hsl", 0, e)
- }, saturation: function (e) {
- return this.setChannel("hsl", 1, e)
- }, lightness: function (e) {
- return this.setChannel("hsl", 2, e)
- }, saturationv: function (e) {
- return this.setChannel("hsv", 1, e)
- }, whiteness: function (e) {
- return this.setChannel("hwb", 1, e)
- }, blackness: function (e) {
- return this.setChannel("hwb", 2, e)
- }, value: function (e) {
- return this.setChannel("hsv", 2, e)
- }, cyan: function (e) {
- return this.setChannel("cmyk", 0, e)
- }, magenta: function (e) {
- return this.setChannel("cmyk", 1, e)
- }, yellow: function (e) {
- return this.setChannel("cmyk", 2, e)
- }, black: function (e) {
- return this.setChannel("cmyk", 3, e)
- }, hexString: function () {
- return n.hexString(this.values.rgb)
- }, rgbString: function () {
- return n.rgbString(this.values.rgb, this.values.alpha)
- }, rgbaString: function () {
- return n.rgbaString(this.values.rgb, this.values.alpha)
- }, percentString: function () {
- return n.percentString(this.values.rgb, this.values.alpha)
- }, hslString: function () {
- return n.hslString(this.values.hsl, this.values.alpha)
- }, hslaString: function () {
- return n.hslaString(this.values.hsl, this.values.alpha)
- }, hwbString: function () {
- return n.hwbString(this.values.hwb, this.values.alpha)
- }, keyword: function () {
- return n.keyword(this.values.rgb, this.values.alpha)
- }, rgbNumber: function () {
- return this.values.rgb[0] << 16 | this.values.rgb[1] << 8 | this.values.rgb[2]
- }, luminosity: function () {
- for (var e = this.values.rgb, r = [], t = 0; t < e.length; t++) {
- var n = e[t] / 255;
- r[t] = .03928 >= n ? n / 12.92 : Math.pow((n + .055) / 1.055, 2.4)
- }
- return .2126 * r[0] + .7152 * r[1] + .0722 * r[2]
- }, contrast: function (e) {
- var r = this.luminosity(), t = e.luminosity();
- return r > t ? (r + .05) / (t + .05) : (t + .05) / (r + .05)
- }, level: function (e) {
- var r = this.contrast(e);
- return r >= 7.1 ? "AAA" : r >= 4.5 ? "AA" : ""
- }, dark: function () {
- var e = this.values.rgb, r = (299 * e[0] + 587 * e[1] + 114 * e[2]) / 1e3;
- return 128 > r
- }, light: function () {
- return !this.dark()
- }, negate: function () {
- for (var e = [], r = 0; 3 > r; r++) e[r] = 255 - this.values.rgb[r];
- return this.setValues("rgb", e), this
- }, lighten: function (e) {
- return this.values.hsl[2] += this.values.hsl[2] * e, this.setValues("hsl", this.values.hsl), this
- }, darken: function (e) {
- return this.values.hsl[2] -= this.values.hsl[2] * e, this.setValues("hsl", this.values.hsl), this
- }, saturate: function (e) {
- return this.values.hsl[1] += this.values.hsl[1] * e, this.setValues("hsl", this.values.hsl), this
- }, desaturate: function (e) {
- return this.values.hsl[1] -= this.values.hsl[1] * e, this.setValues("hsl", this.values.hsl), this
- }, whiten: function (e) {
- return this.values.hwb[1] += this.values.hwb[1] * e, this.setValues("hwb", this.values.hwb), this
- }, blacken: function (e) {
- return this.values.hwb[2] += this.values.hwb[2] * e, this.setValues("hwb", this.values.hwb), this
- }, greyscale: function () {
- var e = this.values.rgb, r = .3 * e[0] + .59 * e[1] + .11 * e[2];
- return this.setValues("rgb", [r, r, r]), this
- }, clearer: function (e) {
- return this.setValues("alpha", this.values.alpha - this.values.alpha * e), this
- }, opaquer: function (e) {
- return this.setValues("alpha", this.values.alpha + this.values.alpha * e), this
- }, rotate: function (e) {
- var r = this.values.hsl[0];
- return r = (r + e) % 360, r = 0 > r ? 360 + r : r, this.values.hsl[0] = r, this.setValues("hsl", this.values.hsl), this
- }, mix: function (e, r) {
- r = 1 - (null == r ? .5 : r);
- for (var t = 2 * r - 1, n = this.alpha() - e.alpha(), a = ((t * n == -1 ? t : (t + n) / (1 + t * n)) + 1) / 2, s = 1 - a, i = this.rgbArray(), u = e.rgbArray(), l = 0; l < i.length; l++) i[l] = i[l] * a + u[l] * s;
- this.setValues("rgb", i);
- var h = this.alpha() * r + e.alpha() * (1 - r);
- return this.setValues("alpha", h), this
- }, toJSON: function () {
- return this.rgb()
- }, clone: function () {
- return new a(this.rgb())
- }
- }, a.prototype.getValues = function (e) {
- for (var r = {}, t = 0; t < e.length; t++) r[e.charAt(t)] = this.values[e][t];
- return 1 != this.values.alpha && (r.a = this.values.alpha), r
- }, a.prototype.setValues = function (e, r) {
- var n = {
- rgb: ["red", "green", "blue"],
- hsl: ["hue", "saturation", "lightness"],
- hsv: ["hue", "saturation", "value"],
- hwb: ["hue", "whiteness", "blackness"],
- cmyk: ["cyan", "magenta", "yellow", "black"]
- }, a = {
- rgb: [255, 255, 255],
- hsl: [360, 100, 100],
- hsv: [360, 100, 100],
- hwb: [360, 100, 100],
- cmyk: [100, 100, 100, 100]
- }, s = 1;
- if ("alpha" == e) s = r; else if (r.length) this.values[e] = r.slice(0, e.length), s = r[e.length]; else if (void 0 !== r[e.charAt(0)]) {
- for (var i = 0; i < e.length; i++) this.values[e][i] = r[e.charAt(i)];
- s = r.a
- } else if (void 0 !== r[n[e][0]]) {
- for (var u = n[e], i = 0; i < e.length; i++) this.values[e][i] = r[u[i]];
- s = r.alpha
- }
- if (this.values.alpha = Math.max(0, Math.min(1, void 0 !== s ? s : this.values.alpha)), "alpha" != e) {
- for (var i = 0; i < e.length; i++) {
- var l = Math.max(0, Math.min(a[e][i], this.values[e][i]));
- this.values[e][i] = Math.round(l)
- }
- for (var h in n) {
- h != e && (this.values[h] = t[e][h](this.values[e]));
- for (var i = 0; i < h.length; i++) {
- var l = Math.max(0, Math.min(a[h][i], this.values[h][i]));
- this.values[h][i] = Math.round(l)
- }
- }
- return !0
- }
- }, a.prototype.setSpace = function (e, r) {
- var t = r[0];
- return void 0 === t ? this.getValues(e) : ("number" == typeof t && (t = Array.prototype.slice.call(r)), this.setValues(e, t), this)
- }, a.prototype.setChannel = function (e, r, t) {
- return void 0 === t ? this.values[e][r] : (this.values[e][r] = t, this.setValues(e, this.values[e]), this)
- }, window.Color = r.exports = a
- }()
- }, {"color-convert": 3, "color-string": 4}], 2: [function (e, t, n) {
- function a(e) {
- var r, t, n, a = e[0] / 255, s = e[1] / 255, i = e[2] / 255, u = Math.min(a, s, i),
- l = Math.max(a, s, i), h = l - u;
- return l == u ? r = 0 : a == l ? r = (s - i) / h : s == l ? r = 2 + (i - a) / h : i == l && (r = 4 + (a - s) / h), r = Math.min(60 * r, 360), 0 > r && (r += 360), n = (u + l) / 2, t = l == u ? 0 : .5 >= n ? h / (l + u) : h / (2 - l - u), [r, 100 * t, 100 * n]
- }
- function s(e) {
- var r, t, n, a = e[0], s = e[1], i = e[2], u = Math.min(a, s, i), l = Math.max(a, s, i), h = l - u;
- return t = 0 == l ? 0 : h / l * 1e3 / 10, l == u ? r = 0 : a == l ? r = (s - i) / h : s == l ? r = 2 + (i - a) / h : i == l && (r = 4 + (a - s) / h), r = Math.min(60 * r, 360), 0 > r && (r += 360), n = l / 255 * 1e3 / 10, [r, t, n]
- }
- function i(e) {
- var r = e[0], t = e[1], n = e[2], s = a(e)[0], i = 1 / 255 * Math.min(r, Math.min(t, n)),
- n = 1 - 1 / 255 * Math.max(r, Math.max(t, n));
- return [s, 100 * i, 100 * n]
- }
- function u(e) {
- var r, t, n, a, s = e[0] / 255, i = e[1] / 255, u = e[2] / 255;
- return a = Math.min(1 - s, 1 - i, 1 - u), r = (1 - s - a) / (1 - a) || 0, t = (1 - i - a) / (1 - a) || 0, n = (1 - u - a) / (1 - a) || 0, [100 * r, 100 * t, 100 * n, 100 * a]
- }
- function l(e) {
- return X[JSON.stringify(e)]
- }
- function h(e) {
- var r = e[0] / 255, t = e[1] / 255, n = e[2] / 255;
- r = r > .04045 ? Math.pow((r + .055) / 1.055, 2.4) : r / 12.92, t = t > .04045 ? Math.pow((t + .055) / 1.055, 2.4) : t / 12.92, n = n > .04045 ? Math.pow((n + .055) / 1.055, 2.4) : n / 12.92;
- var a = .4124 * r + .3576 * t + .1805 * n, s = .2126 * r + .7152 * t + .0722 * n,
- i = .0193 * r + .1192 * t + .9505 * n;
- return [100 * a, 100 * s, 100 * i]
- }
- function o(e) {
- var r, t, n, a = h(e), s = a[0], i = a[1], u = a[2];
- return s /= 95.047, i /= 100, u /= 108.883, s = s > .008856 ? Math.pow(s, 1 / 3) : 7.787 * s + 16 / 116, i = i > .008856 ? Math.pow(i, 1 / 3) : 7.787 * i + 16 / 116, u = u > .008856 ? Math.pow(u, 1 / 3) : 7.787 * u + 16 / 116, r = 116 * i - 16, t = 500 * (s - i), n = 200 * (i - u), [r, t, n]
- }
- function c(e) {
- return J(o(e))
- }
- function v(e) {
- var r, t, n, a, s, i = e[0] / 360, u = e[1] / 100, l = e[2] / 100;
- if (0 == u) return s = 255 * l, [s, s, s];
- t = .5 > l ? l * (1 + u) : l + u - l * u, r = 2 * l - t, a = [0, 0, 0];
- for (var h = 0; 3 > h; h++) n = i + 1 / 3 * -(h - 1), 0 > n && n++, n > 1 && n--, s = 1 > 6 * n ? r + 6 * (t - r) * n : 1 > 2 * n ? t : 2 > 3 * n ? r + (t - r) * (2 / 3 - n) * 6 : r, a[h] = 255 * s;
- return a
- }
- function f(e) {
- var r, t, n = e[0], a = e[1] / 100, s = e[2] / 100;
- return s *= 2, a *= 1 >= s ? s : 2 - s, t = (s + a) / 2, r = 2 * a / (s + a), [n, 100 * r, 100 * t]
- }
- function d(e) {
- return i(v(e))
- }
- function p(e) {
- return u(v(e))
- }
- function m(e) {
- return l(v(e))
- }
- function y(e) {
- var r = e[0] / 60, t = e[1] / 100, n = e[2] / 100, a = Math.floor(r) % 6, s = r - Math.floor(r),
- i = 255 * n * (1 - t), u = 255 * n * (1 - t * s), l = 255 * n * (1 - t * (1 - s)), n = 255 * n;
- switch (a) {
- case 0:
- return [n, l, i];
- case 1:
- return [u, n, i];
- case 2:
- return [i, n, l];
- case 3:
- return [i, u, n];
- case 4:
- return [l, i, n];
- case 5:
- return [n, i, u]
- }
- }
- function w(e) {
- var r, t, n = e[0], a = e[1] / 100, s = e[2] / 100;
- return t = (2 - a) * s, r = a * s, r /= 1 >= t ? t : 2 - t, r = r || 0, t /= 2, [n, 100 * r, 100 * t]
- }
- function k(e) {
- return i(y(e))
- }
- function M(e) {
- return u(y(e))
- }
- function S(e) {
- return l(y(e))
- }
- function x(e) {
- var t, n, a, s, i = e[0] / 360, u = e[1] / 100, l = e[2] / 100, h = u + l;
- switch (h > 1 && (u /= h, l /= h), t = Math.floor(6 * i), n = 1 - l, a = 6 * i - t, 0 != (1 & t) && (a = 1 - a), s = u + a * (n - u), t) {
- default:
- case 6:
- case 0:
- r = n, g = s, b = u;
- break;
- case 1:
- r = s, g = n, b = u;
- break;
- case 2:
- r = u, g = n, b = s;
- break;
- case 3:
- r = u, g = s, b = n;
- break;
- case 4:
- r = s, g = u, b = n;
- break;
- case 5:
- r = n, g = u, b = s
- }
- return [255 * r, 255 * g, 255 * b]
- }
- function V(e) {
- return a(x(e))
- }
- function q(e) {
- return s(x(e))
- }
- function A(e) {
- return u(x(e))
- }
- function C(e) {
- return l(x(e))
- }
- function F(e) {
- var r, t, n, a = e[0] / 100, s = e[1] / 100, i = e[2] / 100, u = e[3] / 100;
- return r = 1 - Math.min(1, a * (1 - u) + u), t = 1 - Math.min(1, s * (1 - u) + u), n = 1 - Math.min(1, i * (1 - u) + u), [255 * r, 255 * t, 255 * n]
- }
- function N(e) {
- return a(F(e))
- }
- function z(e) {
- return s(F(e))
- }
- function I(e) {
- return i(F(e))
- }
- function O(e) {
- return l(F(e))
- }
- function E(e) {
- var r, t, n, a = e[0] / 100, s = e[1] / 100, i = e[2] / 100;
- return r = 3.2406 * a + -1.5372 * s + i * -.4986, t = a * -.9689 + 1.8758 * s + .0415 * i, n = .0557 * a + s * -.204 + 1.057 * i, r = r > .0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - .055 : r = 12.92 * r, t = t > .0031308 ? 1.055 * Math.pow(t, 1 / 2.4) - .055 : t = 12.92 * t, n = n > .0031308 ? 1.055 * Math.pow(n, 1 / 2.4) - .055 : n = 12.92 * n, r = Math.min(Math.max(0, r), 1), t = Math.min(Math.max(0, t), 1), n = Math.min(Math.max(0, n), 1), [255 * r, 255 * t, 255 * n]
- }
- function H(e) {
- var r, t, n, a = e[0], s = e[1], i = e[2];
- return a /= 95.047, s /= 100, i /= 108.883, a = a > .008856 ? Math.pow(a, 1 / 3) : 7.787 * a + 16 / 116, s = s > .008856 ? Math.pow(s, 1 / 3) : 7.787 * s + 16 / 116, i = i > .008856 ? Math.pow(i, 1 / 3) : 7.787 * i + 16 / 116, r = 116 * s - 16, t = 500 * (a - s), n = 200 * (s - i), [r, t, n]
- }
- function U(e) {
- return J(H(e))
- }
- function j(e) {
- var r, t, n, a, s = e[0], i = e[1], u = e[2];
- return 8 >= s ? (t = 100 * s / 903.3, a = 7.787 * (t / 100) + 16 / 116) : (t = 100 * Math.pow((s + 16) / 116, 3), a = Math.pow(t / 100, 1 / 3)), r = .008856 >= r / 95.047 ? r = 95.047 * (i / 500 + a - 16 / 116) / 7.787 : 95.047 * Math.pow(i / 500 + a, 3), n = .008859 >= n / 108.883 ? n = 108.883 * (a - u / 200 - 16 / 116) / 7.787 : 108.883 * Math.pow(a - u / 200, 3), [r, t, n]
- }
- function J(e) {
- var r, t, n, a = e[0], s = e[1], i = e[2];
- return r = Math.atan2(i, s), t = 360 * r / 2 / Math.PI, 0 > t && (t += 360), n = Math.sqrt(s * s + i * i), [a, n, t]
- }
- function R(e) {
- return E(j(e))
- }
- function $(e) {
- var r, t, n, a = e[0], s = e[1], i = e[2];
- return n = i / 360 * 2 * Math.PI, r = s * Math.cos(n), t = s * Math.sin(n), [a, r, t]
- }
- function D(e) {
- return j($(e))
- }
- function P(e) {
- return R($(e))
- }
- function _(e) {
- return W[e]
- }
- function L(e) {
- return a(_(e))
- }
- function T(e) {
- return s(_(e))
- }
- function B(e) {
- return i(_(e))
- }
- function G(e) {
- return u(_(e))
- }
- function K(e) {
- return o(_(e))
- }
- function Q(e) {
- return h(_(e))
- }
- t.exports = {
- rgb2hsl: a,
- rgb2hsv: s,
- rgb2hwb: i,
- rgb2cmyk: u,
- rgb2keyword: l,
- rgb2xyz: h,
- rgb2lab: o,
- rgb2lch: c,
- hsl2rgb: v,
- hsl2hsv: f,
- hsl2hwb: d,
- hsl2cmyk: p,
- hsl2keyword: m,
- hsv2rgb: y,
- hsv2hsl: w,
- hsv2hwb: k,
- hsv2cmyk: M,
- hsv2keyword: S,
- hwb2rgb: x,
- hwb2hsl: V,
- hwb2hsv: q,
- hwb2cmyk: A,
- hwb2keyword: C,
- cmyk2rgb: F,
- cmyk2hsl: N,
- cmyk2hsv: z,
- cmyk2hwb: I,
- cmyk2keyword: O,
- keyword2rgb: _,
- keyword2hsl: L,
- keyword2hsv: T,
- keyword2hwb: B,
- keyword2cmyk: G,
- keyword2lab: K,
- keyword2xyz: Q,
- xyz2rgb: E,
- xyz2lab: H,
- xyz2lch: U,
- lab2xyz: j,
- lab2rgb: R,
- lab2lch: J,
- lch2lab: $,
- lch2xyz: D,
- lch2rgb: P
- };
- var W = {
- aliceblue: [240, 248, 255],
- antiquewhite: [250, 235, 215],
- aqua: [0, 255, 255],
- aquamarine: [127, 255, 212],
- azure: [240, 255, 255],
- beige: [245, 245, 220],
- bisque: [255, 228, 196],
- black: [0, 0, 0],
- blanchedalmond: [255, 235, 205],
- blue: [0, 0, 255],
- blueviolet: [138, 43, 226],
- brown: [165, 42, 42],
- burlywood: [222, 184, 135],
- cadetblue: [95, 158, 160],
- chartreuse: [127, 255, 0],
- chocolate: [210, 105, 30],
- coral: [255, 127, 80],
- cornflowerblue: [100, 149, 237],
- cornsilk: [255, 248, 220],
- crimson: [220, 20, 60],
- cyan: [0, 255, 255],
- darkblue: [0, 0, 139],
- darkcyan: [0, 139, 139],
- darkgoldenrod: [184, 134, 11],
- darkgray: [169, 169, 169],
- darkgreen: [0, 100, 0],
- darkgrey: [169, 169, 169],
- 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],
- darkseagreen: [143, 188, 143],
- darkslateblue: [72, 61, 139],
- darkslategray: [47, 79, 79],
- darkslategrey: [47, 79, 79],
- darkturquoise: [0, 206, 209],
- darkviolet: [148, 0, 211],
- deeppink: [255, 20, 147],
- deepskyblue: [0, 191, 255],
- dimgray: [105, 105, 105],
- dimgrey: [105, 105, 105],
- dodgerblue: [30, 144, 255],
- firebrick: [178, 34, 34],
- floralwhite: [255, 250, 240],
- forestgreen: [34, 139, 34],
- fuchsia: [255, 0, 255],
- gainsboro: [220, 220, 220],
- ghostwhite: [248, 248, 255],
- gold: [255, 215, 0],
- goldenrod: [218, 165, 32],
- gray: [128, 128, 128],
- green: [0, 128, 0],
- greenyellow: [173, 255, 47],
- grey: [128, 128, 128],
- honeydew: [240, 255, 240],
- hotpink: [255, 105, 180],
- indianred: [205, 92, 92],
- indigo: [75, 0, 130],
- ivory: [255, 255, 240],
- khaki: [240, 230, 140],
- lavender: [230, 230, 250],
- lavenderblush: [255, 240, 245],
- lawngreen: [124, 252, 0],
- lemonchiffon: [255, 250, 205],
- lightblue: [173, 216, 230],
- lightcoral: [240, 128, 128],
- lightcyan: [224, 255, 255],
- lightgoldenrodyellow: [250, 250, 210],
- lightgray: [211, 211, 211],
- lightgreen: [144, 238, 144],
- lightgrey: [211, 211, 211],
- lightpink: [255, 182, 193],
- lightsalmon: [255, 160, 122],
- lightseagreen: [32, 178, 170],
- lightskyblue: [135, 206, 250],
- lightslategray: [119, 136, 153],
- lightslategrey: [119, 136, 153],
- lightsteelblue: [176, 196, 222],
- lightyellow: [255, 255, 224],
- lime: [0, 255, 0],
- limegreen: [50, 205, 50],
- linen: [250, 240, 230],
- magenta: [255, 0, 255],
- maroon: [128, 0, 0],
- mediumaquamarine: [102, 205, 170],
- mediumblue: [0, 0, 205],
- mediumorchid: [186, 85, 211],
- mediumpurple: [147, 112, 219],
- mediumseagreen: [60, 179, 113],
- mediumslateblue: [123, 104, 238],
- mediumspringgreen: [0, 250, 154],
- mediumturquoise: [72, 209, 204],
- mediumvioletred: [199, 21, 133],
- midnightblue: [25, 25, 112],
- mintcream: [245, 255, 250],
- mistyrose: [255, 228, 225],
- moccasin: [255, 228, 181],
- navajowhite: [255, 222, 173],
- navy: [0, 0, 128],
- oldlace: [253, 245, 230],
- olive: [128, 128, 0],
- olivedrab: [107, 142, 35],
- orange: [255, 165, 0],
- orangered: [255, 69, 0],
- orchid: [218, 112, 214],
- palegoldenrod: [238, 232, 170],
- palegreen: [152, 251, 152],
- paleturquoise: [175, 238, 238],
- palevioletred: [219, 112, 147],
- papayawhip: [255, 239, 213],
- peachpuff: [255, 218, 185],
- peru: [205, 133, 63],
- pink: [255, 192, 203],
- plum: [221, 160, 221],
- powderblue: [176, 224, 230],
- purple: [128, 0, 128],
- rebeccapurple: [102, 51, 153],
- red: [255, 0, 0],
- rosybrown: [188, 143, 143],
- royalblue: [65, 105, 225],
- saddlebrown: [139, 69, 19],
- salmon: [250, 128, 114],
- sandybrown: [244, 164, 96],
- seagreen: [46, 139, 87],
- seashell: [255, 245, 238],
- sienna: [160, 82, 45],
- silver: [192, 192, 192],
- skyblue: [135, 206, 235],
- slateblue: [106, 90, 205],
- slategray: [112, 128, 144],
- slategrey: [112, 128, 144],
- snow: [255, 250, 250],
- springgreen: [0, 255, 127],
- steelblue: [70, 130, 180],
- tan: [210, 180, 140],
- teal: [0, 128, 128],
- thistle: [216, 191, 216],
- tomato: [255, 99, 71],
- turquoise: [64, 224, 208],
- violet: [238, 130, 238],
- wheat: [245, 222, 179],
- white: [255, 255, 255],
- whitesmoke: [245, 245, 245],
- yellow: [255, 255, 0],
- yellowgreen: [154, 205, 50]
- }, X = {};
- for (var Y in W) X[JSON.stringify(W[Y])] = Y
- }, {}], 3: [function (e, r, t) {
- var n = e("./conversions"), a = function () {
- return new h
- };
- for (var s in n) {
- a[s + "Raw"] = function (e) {
- return function (r) {
- return "number" == typeof r && (r = Array.prototype.slice.call(arguments)), n[e](r)
- }
- }(s);
- var i = /(\w+)2(\w+)/.exec(s), u = i[1], l = i[2];
- a[u] = a[u] || {}, a[u][l] = a[s] = function (e) {
- return function (r) {
- "number" == typeof r && (r = Array.prototype.slice.call(arguments));
- var t = n[e](r);
- if ("string" == typeof t || void 0 === t) return t;
- for (var a = 0; a < t.length; a++) t[a] = Math.round(t[a]);
- return t
- }
- }(s)
- }
- var h = function () {
- this.convs = {}
- };
- h.prototype.routeSpace = function (e, r) {
- var t = r[0];
- return void 0 === t ? this.getValues(e) : ("number" == typeof t && (t = Array.prototype.slice.call(r)), this.setValues(e, t))
- }, h.prototype.setValues = function (e, r) {
- return this.space = e, this.convs = {}, this.convs[e] = r, this
- }, h.prototype.getValues = function (e) {
- var r = this.convs[e];
- if (!r) {
- var t = this.space, n = this.convs[t];
- r = a[t][e](n), this.convs[e] = r
- }
- return r
- }, ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function (e) {
- h.prototype[e] = function (r) {
- return this.routeSpace(e, arguments)
- }
- }), r.exports = a
- }, {"./conversions": 2}], 4: [function (e, r, t) {
- function n(e) {
- if (e) {
- var r = /^#([a-fA-F0-9]{3})$/, t = /^#([a-fA-F0-9]{6})$/,
- n = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
- a = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
- s = /(\w+)/, i = [0, 0, 0], u = 1, l = e.match(r);
- if (l) {
- l = l[1];
- for (var h = 0; h < i.length; h++) i[h] = parseInt(l[h] + l[h], 16)
- } else if (l = e.match(t)) {
- l = l[1];
- for (var h = 0; h < i.length; h++) i[h] = parseInt(l.slice(2 * h, 2 * h + 2), 16)
- } else if (l = e.match(n)) {
- for (var h = 0; h < i.length; h++) i[h] = parseInt(l[h + 1]);
- u = parseFloat(l[4])
- } else if (l = e.match(a)) {
- for (var h = 0; h < i.length; h++) i[h] = Math.round(2.55 * parseFloat(l[h + 1]));
- u = parseFloat(l[4])
- } else if (l = e.match(s)) {
- if ("transparent" == l[1]) return [0, 0, 0, 0];
- if (i = w[l[1]], !i) return
- }
- for (var h = 0; h < i.length; h++) i[h] = m(i[h], 0, 255);
- return u = u || 0 == u ? m(u, 0, 1) : 1, i[3] = u, i
- }
- }
- function a(e) {
- if (e) {
- var r = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/,
- t = e.match(r);
- if (t) {
- var n = parseFloat(t[4]), a = m(parseInt(t[1]), 0, 360), s = m(parseFloat(t[2]), 0, 100),
- i = m(parseFloat(t[3]), 0, 100), u = m(isNaN(n) ? 1 : n, 0, 1);
- return [a, s, i, u]
- }
- }
- }
- function s(e) {
- if (e) {
- var r = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/,
- t = e.match(r);
- if (t) {
- var n = parseFloat(t[4]), a = m(parseInt(t[1]), 0, 360), s = m(parseFloat(t[2]), 0, 100),
- i = m(parseFloat(t[3]), 0, 100), u = m(isNaN(n) ? 1 : n, 0, 1);
- return [a, s, i, u]
- }
- }
- }
- function i(e) {
- var r = n(e);
- return r && r.slice(0, 3)
- }
- function u(e) {
- var r = a(e);
- return r && r.slice(0, 3)
- }
- function l(e) {
- var r = n(e);
- return r ? r[3] : (r = a(e)) ? r[3] : (r = s(e)) ? r[3] : void 0
- }
- function h(e) {
- return "#" + y(e[0]) + y(e[1]) + y(e[2])
- }
- function o(e, r) {
- return 1 > r || e[3] && e[3] < 1 ? c(e, r) : "rgb(" + e[0] + ", " + e[1] + ", " + e[2] + ")"
- }
- function c(e, r) {
- return void 0 === r && (r = void 0 !== e[3] ? e[3] : 1), "rgba(" + e[0] + ", " + e[1] + ", " + e[2] + ", " + r + ")"
- }
- function g(e, r) {
- if (1 > r || e[3] && e[3] < 1) return v(e, r);
- var t = Math.round(e[0] / 255 * 100), n = Math.round(e[1] / 255 * 100),
- a = Math.round(e[2] / 255 * 100);
- return "rgb(" + t + "%, " + n + "%, " + a + "%)"
- }
- function v(e, r) {
- var t = Math.round(e[0] / 255 * 100), n = Math.round(e[1] / 255 * 100),
- a = Math.round(e[2] / 255 * 100);
- return "rgba(" + t + "%, " + n + "%, " + a + "%, " + (r || e[3] || 1) + ")"
- }
- function f(e, r) {
- return 1 > r || e[3] && e[3] < 1 ? d(e, r) : "hsl(" + e[0] + ", " + e[1] + "%, " + e[2] + "%)"
- }
- function d(e, r) {
- return void 0 === r && (r = void 0 !== e[3] ? e[3] : 1), "hsla(" + e[0] + ", " + e[1] + "%, " + e[2] + "%, " + r + ")"
- }
- function b(e, r) {
- return void 0 === r && (r = void 0 !== e[3] ? e[3] : 1), "hwb(" + e[0] + ", " + e[1] + "%, " + e[2] + "%" + (void 0 !== r && 1 !== r ? ", " + r : "") + ")"
- }
- function p(e) {
- return k[e.slice(0, 3)]
- }
- function m(e, r, t) {
- return Math.min(Math.max(r, e), t)
- }
- function y(e) {
- var r = e.toString(16).toUpperCase();
- return r.length < 2 ? "0" + r : r
- }
- var w = e("color-name");
- r.exports = {
- getRgba: n,
- getHsla: a,
- getRgb: i,
- getHsl: u,
- getHwb: s,
- getAlpha: l,
- hexString: h,
- rgbString: o,
- rgbaString: c,
- percentString: g,
- percentaString: v,
- hslString: f,
- hslaString: d,
- hwbString: b,
- keyword: p
- };
- var k = {};
- for (var M in w) k[w[M]] = M
- }, {"color-name": 5}], 5: [function (e, r, t) {
- r.exports = {
- aliceblue: [240, 248, 255],
- antiquewhite: [250, 235, 215],
- aqua: [0, 255, 255],
- aquamarine: [127, 255, 212],
- azure: [240, 255, 255],
- beige: [245, 245, 220],
- bisque: [255, 228, 196],
- black: [0, 0, 0],
- blanchedalmond: [255, 235, 205],
- blue: [0, 0, 255],
- blueviolet: [138, 43, 226],
- brown: [165, 42, 42],
- burlywood: [222, 184, 135],
- cadetblue: [95, 158, 160],
- chartreuse: [127, 255, 0],
- chocolate: [210, 105, 30],
- coral: [255, 127, 80],
- cornflowerblue: [100, 149, 237],
- cornsilk: [255, 248, 220],
- crimson: [220, 20, 60],
- cyan: [0, 255, 255],
- darkblue: [0, 0, 139],
- darkcyan: [0, 139, 139],
- darkgoldenrod: [184, 134, 11],
- darkgray: [169, 169, 169],
- darkgreen: [0, 100, 0],
- darkgrey: [169, 169, 169],
- 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],
- darkseagreen: [143, 188, 143],
- darkslateblue: [72, 61, 139],
- darkslategray: [47, 79, 79],
- darkslategrey: [47, 79, 79],
- darkturquoise: [0, 206, 209],
- darkviolet: [148, 0, 211],
- deeppink: [255, 20, 147],
- deepskyblue: [0, 191, 255],
- dimgray: [105, 105, 105],
- dimgrey: [105, 105, 105],
- dodgerblue: [30, 144, 255],
- firebrick: [178, 34, 34],
- floralwhite: [255, 250, 240],
- forestgreen: [34, 139, 34],
- fuchsia: [255, 0, 255],
- gainsboro: [220, 220, 220],
- ghostwhite: [248, 248, 255],
- gold: [255, 215, 0],
- goldenrod: [218, 165, 32],
- gray: [128, 128, 128],
- green: [0, 128, 0],
- greenyellow: [173, 255, 47],
- grey: [128, 128, 128],
- honeydew: [240, 255, 240],
- hotpink: [255, 105, 180],
- indianred: [205, 92, 92],
- indigo: [75, 0, 130],
- ivory: [255, 255, 240],
- khaki: [240, 230, 140],
- lavender: [230, 230, 250],
- lavenderblush: [255, 240, 245],
- lawngreen: [124, 252, 0],
- lemonchiffon: [255, 250, 205],
- lightblue: [173, 216, 230],
- lightcoral: [240, 128, 128],
- lightcyan: [224, 255, 255],
- lightgoldenrodyellow: [250, 250, 210],
- lightgray: [211, 211, 211],
- lightgreen: [144, 238, 144],
- lightgrey: [211, 211, 211],
- lightpink: [255, 182, 193],
- lightsalmon: [255, 160, 122],
- lightseagreen: [32, 178, 170],
- lightskyblue: [135, 206, 250],
- lightslategray: [119, 136, 153],
- lightslategrey: [119, 136, 153],
- lightsteelblue: [176, 196, 222],
- lightyellow: [255, 255, 224],
- lime: [0, 255, 0],
- limegreen: [50, 205, 50],
- linen: [250, 240, 230],
- magenta: [255, 0, 255],
- maroon: [128, 0, 0],
- mediumaquamarine: [102, 205, 170],
- mediumblue: [0, 0, 205],
- mediumorchid: [186, 85, 211],
- mediumpurple: [147, 112, 219],
- mediumseagreen: [60, 179, 113],
- mediumslateblue: [123, 104, 238],
- mediumspringgreen: [0, 250, 154],
- mediumturquoise: [72, 209, 204],
- mediumvioletred: [199, 21, 133],
- midnightblue: [25, 25, 112],
- mintcream: [245, 255, 250],
- mistyrose: [255, 228, 225],
- moccasin: [255, 228, 181],
- navajowhite: [255, 222, 173],
- navy: [0, 0, 128],
- oldlace: [253, 245, 230],
- olive: [128, 128, 0],
- olivedrab: [107, 142, 35],
- orange: [255, 165, 0],
- orangered: [255, 69, 0],
- orchid: [218, 112, 214],
- palegoldenrod: [238, 232, 170],
- palegreen: [152, 251, 152],
- paleturquoise: [175, 238, 238],
- palevioletred: [219, 112, 147],
- papayawhip: [255, 239, 213],
- peachpuff: [255, 218, 185],
- peru: [205, 133, 63],
- pink: [255, 192, 203],
- plum: [221, 160, 221],
- powderblue: [176, 224, 230],
- purple: [128, 0, 128],
- rebeccapurple: [102, 51, 153],
- red: [255, 0, 0],
- rosybrown: [188, 143, 143],
- royalblue: [65, 105, 225],
- saddlebrown: [139, 69, 19],
- salmon: [250, 128, 114],
- sandybrown: [244, 164, 96],
- seagreen: [46, 139, 87],
- seashell: [255, 245, 238],
- sienna: [160, 82, 45],
- silver: [192, 192, 192],
- skyblue: [135, 206, 235],
- slateblue: [106, 90, 205],
- slategray: [112, 128, 144],
- slategrey: [112, 128, 144],
- snow: [255, 250, 250],
- springgreen: [0, 255, 127],
- steelblue: [70, 130, 180],
- tan: [210, 180, 140],
- teal: [0, 128, 128],
- thistle: [216, 191, 216],
- tomato: [255, 99, 71],
- turquoise: [64, 224, 208],
- violet: [238, 130, 238],
- wheat: [245, 222, 179],
- white: [255, 255, 255],
- whitesmoke: [245, 245, 245],
- yellow: [255, 255, 0],
- yellowgreen: [154, 205, 50]
- }
- }, {}]
- }, {}, [1]);
- /*!
- * Chart.js
- * http://chartjs.org/
- * Version: 2.0.0-beta2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
- (function () {
- "use strict";
- //Declare root variable - window in the browser, global on the server
- var root = this,
- previous = root.Chart;
- //Occupy the global variable of Chart, and create a simple base class
- var Chart = function (context, config) {
- this.config = config;
- // Support a jQuery'd canvas element
- if (context.length && context[0].getContext) {
- context = context[0];
- }
- // Support a canvas domnode
- if (context.getContext) {
- context = context.getContext("2d");
- }
- this.ctx = context;
- this.canvas = context.canvas;
- // Figure out what the size of the chart will be.
- // If the canvas has a specified width and height, we use those else
- // we look to see if the canvas node has a CSS width and height.
- // If there is still no height, fill the parent container
- this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas);
- this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas);
- this.aspectRatio = this.width / this.height;
- if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) {
- // If the canvas has no size, try and figure out what the aspect ratio will be.
- // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
- // else use the canvas default ratio of 2
- this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
- }
- // Store the original style of the element so we can set it back
- this.originalCanvasStyleWidth = context.canvas.style.width;
- this.originalCanvasStyleHeight = context.canvas.style.height;
- // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
- Chart.helpers.retinaScale(this);
- if (config) {
- this.controller = new Chart.Controller(this);
- }
- // Always bind this so that if the responsive state changes we still work
- var _this = this;
- Chart.helpers.addResizeListener(context.canvas.parentNode, function () {
- if (_this.controller && _this.controller.config.options.responsive) {
- _this.controller.resize();
- }
- });
- return this.controller ? this.controller : this;
- };
- //Globally expose the defaults to allow for user updating/changing
- Chart.defaults = {
- global: {
- responsive: true,
- responsiveAnimationDuration: 0,
- maintainAspectRatio: true,
- events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"],
- hover: {
- onHover: null,
- mode: 'single',
- animationDuration: 400,
- },
- onClick: null,
- defaultColor: 'rgba(0,0,0,0.1)',
- // Element defaults defined in element extensions
- elements: {},
- // Legend callback string
- legendCallback: function (chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
- for (var i = 0; i < chart.data.datasets.length; i++) {
- text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '">');
- if (chart.data.datasets[i].label) {
- text.push(chart.data.datasets[i].label);
- }
- text.push('</span></li>');
- }
- text.push('</ul>');
- return text.join("");
- }
- },
- };
- root.Chart = Chart;
- Chart.noConflict = function () {
- root.Chart = previous;
- return Chart;
- };
- }).call(this);
- (function () {
- "use strict";
- //Declare root variable - window in the browser, global on the server
- var root = this,
- Chart = root.Chart;
- //Global Chart helpers object for utility methods and classes
- var helpers = Chart.helpers = {};
- //-- Basic js utility methods
- helpers.each = function (loopable, callback, self, reverse) {
- var additionalArgs = Array.prototype.slice.call(arguments, 3);
- // Check to see if null or undefined firstly.
- if (loopable) {
- if (loopable.length === +loopable.length) {
- var i;
- if (reverse) {
- for (i = loopable.length - 1; i >= 0; i--) {
- callback.apply(self, [loopable[i], i].concat(additionalArgs));
- }
- } else {
- for (i = 0; i < loopable.length; i++) {
- callback.apply(self, [loopable[i], i].concat(additionalArgs));
- }
- }
- } else {
- for (var item in loopable) {
- callback.apply(self, [loopable[item], item].concat(additionalArgs));
- }
- }
- }
- };
- helpers.clone = function (obj) {
- var objClone = {};
- helpers.each(obj, function (value, key) {
- if (obj.hasOwnProperty(key)) {
- if (helpers.isArray(value)) {
- objClone[key] = value.slice(0);
- } else if (typeof value === 'object' && value !== null) {
- objClone[key] = helpers.clone(value);
- } else {
- objClone[key] = value;
- }
- }
- });
- return objClone;
- };
- helpers.extend = function (base) {
- helpers.each(Array.prototype.slice.call(arguments, 1), function (extensionObject) {
- helpers.each(extensionObject, function (value, key) {
- if (extensionObject.hasOwnProperty(key)) {
- base[key] = value;
- }
- });
- });
- return base;
- };
- // Need a special merge function to chart configs since they are now grouped
- helpers.configMerge = function (_base) {
- var base = helpers.clone(_base);
- helpers.each(Array.prototype.slice.call(arguments, 1), function (extension) {
- helpers.each(extension, function (value, key) {
- if (extension.hasOwnProperty(key)) {
- if (key === 'scales') {
- // Scale config merging is complex. Add out own function here for that
- base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
- } else if (key === 'scale') {
- // Used in polar area & radar charts since there is only one scale
- base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
- } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
- // In this case we have an array of objects replacing another array. Rather than doing a strict replace,
- // merge. This allows easy scale option merging
- var baseArray = base[key];
- helpers.each(value, function (valueObj, index) {
- if (index < baseArray.length) {
- if (typeof baseArray[index] == 'object' && baseArray[index] !== null && typeof valueObj == 'object' && valueObj !== null) {
- // Two objects are coming together. Do a merge of them.
- baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
- } else {
- // Just overwrite in this case since there is nothing to merge
- baseArray[index] = valueObj;
- }
- } else {
- baseArray.push(valueObj); // nothing to merge
- }
- });
- } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") {
- // If we are overwriting an object with an object, do a merge of the properties.
- base[key] = helpers.configMerge(base[key], value);
- } else {
- // can just overwrite the value in this case
- base[key] = value;
- }
- }
- });
- });
- return base;
- };
- helpers.extendDeep = function (_base) {
- return _extendDeep.apply(this, arguments);
- function _extendDeep(dst) {
- helpers.each(arguments, function (obj) {
- if (obj !== dst) {
- helpers.each(obj, function (value, key) {
- if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
- _extendDeep(dst[key], value);
- } else {
- dst[key] = value;
- }
- });
- }
- });
- return dst;
- }
- };
- helpers.scaleMerge = function (_base, extension) {
- var base = helpers.clone(_base);
- helpers.each(extension, function (value, key) {
- if (extension.hasOwnProperty(key)) {
- if (key === 'xAxes' || key === 'yAxes') {
- // These properties are arrays of items
- if (base.hasOwnProperty(key)) {
- helpers.each(value, function (valueObj, index) {
- if (index >= base[key].length || !base[key][index].type) {
- base[key].push(helpers.configMerge(valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj));
- } else if (valueObj.type !== base[key][index].type) {
- // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
- base[key][index] = helpers.configMerge(base[key][index], valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj);
- } else {
- // Type is the same
- base[key][index] = helpers.configMerge(base[key][index], valueObj);
- }
- });
- } else {
- base[key] = [];
- helpers.each(value, function (valueObj) {
- base[key].push(helpers.configMerge(valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj));
- });
- }
- } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") {
- // If we are overwriting an object with an object, do a merge of the properties.
- base[key] = helpers.configMerge(base[key], value);
- } else {
- // can just overwrite the value in this case
- base[key] = value;
- }
- }
- });
- return base;
- };
- helpers.getValueAtIndexOrDefault = function (value, index, defaultValue) {
- if (value === undefined || value === null) {
- return defaultValue;
- }
- if (helpers.isArray(value)) {
- return index < value.length ? value[index] : defaultValue;
- }
- return value;
- };
- helpers.getValueOrDefault = function (value, defaultValue) {
- return value === undefined ? defaultValue : value;
- };
- helpers.indexOf = function (arrayToSearch, item) {
- if (Array.prototype.indexOf) {
- return arrayToSearch.indexOf(item);
- } else {
- for (var i = 0; i < arrayToSearch.length; i++) {
- if (arrayToSearch[i] === item) return i;
- }
- return -1;
- }
- };
- helpers.where = function (collection, filterCallback) {
- var filtered = [];
- helpers.each(collection, function (item) {
- if (filterCallback(item)) {
- filtered.push(item);
- }
- });
- return filtered;
- };
- helpers.findNextWhere = function (arrayToSearch, filterCallback, startIndex) {
- // Default to start of the array
- if (startIndex === undefined || startIndex === null) {
- startIndex = -1;
- }
- for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
- var currentItem = arrayToSearch[i];
- if (filterCallback(currentItem)) {
- return currentItem;
- }
- }
- };
- helpers.findPreviousWhere = function (arrayToSearch, filterCallback, startIndex) {
- // Default to end of the array
- if (startIndex === undefined || startIndex === null) {
- startIndex = arrayToSearch.length;
- }
- for (var i = startIndex - 1; i >= 0; i--) {
- var currentItem = arrayToSearch[i];
- if (filterCallback(currentItem)) {
- return currentItem;
- }
- }
- };
- helpers.inherits = function (extensions) {
- //Basic javascript inheritance based on the model created in Backbone.js
- var parent = this;
- var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function () {
- return parent.apply(this, arguments);
- };
- var Surrogate = function () {
- this.constructor = ChartElement;
- };
- Surrogate.prototype = parent.prototype;
- ChartElement.prototype = new Surrogate();
- ChartElement.extend = helpers.inherits;
- if (extensions) {
- helpers.extend(ChartElement.prototype, extensions);
- }
- ChartElement.__super__ = parent.prototype;
- return ChartElement;
- };
- helpers.noop = function () {
- };
- helpers.uid = (function () {
- var id = 0;
- return function () {
- return "chart-" + id++;
- };
- })();
- helpers.warn = function (str) {
- //Method for warning of errors
- if (console && typeof console.warn === "function") {
- console.warn(str);
- }
- };
- //-- Math methods
- helpers.isNumber = function (n) {
- return !isNaN(parseFloat(n)) && isFinite(n);
- };
- helpers.max = function (array) {
- return Math.max.apply(Math, array);
- };
- helpers.min = function (array) {
- return Math.min.apply(Math, array);
- };
- helpers.sign = function (x) {
- if (Math.sign) {
- return Math.sign(x);
- } else {
- x = +x; // convert to a number
- if (x === 0 || isNaN(x)) {
- return x;
- }
- return x > 0 ? 1 : -1;
- }
- };
- helpers.log10 = function (x) {
- if (Math.log10) {
- return Math.log10(x);
- } else {
- return Math.log(x) / Math.LN10;
- }
- };
- helpers.toRadians = function (degrees) {
- return degrees * (Math.PI / 180);
- };
- helpers.toDegrees = function (radians) {
- return radians * (180 / Math.PI);
- };
- // Gets the angle from vertical upright to the point about a centre.
- helpers.getAngleFromPoint = function (centrePoint, anglePoint) {
- var distanceFromXCenter = anglePoint.x - centrePoint.x,
- distanceFromYCenter = anglePoint.y - centrePoint.y,
- radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
- var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
- if (angle < (-0.5 * Math.PI)) {
- angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
- }
- return {
- angle: angle,
- distance: radialDistanceFromCenter
- };
- };
- helpers.aliasPixel = function (pixelWidth) {
- return (pixelWidth % 2 === 0) ? 0 : 0.5;
- };
- helpers.splineCurve = function (firstPoint, middlePoint, afterPoint, t) {
- //Props to Rob Spencer at scaled innovation for his post on splining between points
- //http://scaledinnovation.com/analytics/splines/aboutSplines.html
- // This function must also respect "skipped" points
- var previous = firstPoint.skip ? middlePoint : firstPoint,
- current = middlePoint,
- next = afterPoint.skip ? middlePoint : afterPoint;
- var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
- var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
- var s01 = d01 / (d01 + d12);
- var s12 = d12 / (d01 + d12);
- // If all points are the same, s01 & s02 will be inf
- s01 = isNaN(s01) ? 0 : s01;
- s12 = isNaN(s12) ? 0 : s12;
- var fa = t * s01; // scaling factor for triangle Ta
- var fb = t * s12;
- return {
- previous: {
- x: current.x - fa * (next.x - previous.x),
- y: current.y - fa * (next.y - previous.y)
- },
- next: {
- x: current.x + fb * (next.x - previous.x),
- y: current.y + fb * (next.y - previous.y)
- }
- };
- };
- helpers.nextItem = function (collection, index, loop) {
- if (loop) {
- return index >= collection.length - 1 ? collection[0] : collection[index + 1];
- }
- return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
- };
- helpers.previousItem = function (collection, index, loop) {
- if (loop) {
- return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
- }
- return index <= 0 ? collection[0] : collection[index - 1];
- };
- // Implementation of the nice number algorithm used in determining where axis labels will go
- helpers.niceNum = function (range, round) {
- var exponent = Math.floor(helpers.log10(range));
- var fraction = range / Math.pow(10, exponent);
- var niceFraction;
- if (round) {
- if (fraction < 1.5) {
- niceFraction = 1;
- } else if (fraction < 3) {
- niceFraction = 2;
- } else if (fraction < 7) {
- niceFraction = 5;
- } else {
- niceFraction = 10;
- }
- } else {
- if (fraction <= 1.0) {
- niceFraction = 1;
- } else if (fraction <= 2) {
- niceFraction = 2;
- } else if (fraction <= 5) {
- niceFraction = 5;
- } else {
- niceFraction = 10;
- }
- }
- return niceFraction * Math.pow(10, exponent);
- };
- //Easing functions adapted from Robert Penner's easing equations
- //http://www.robertpenner.com/easing/
- var easingEffects = helpers.easingEffects = {
- linear: function (t) {
- return t;
- },
- easeInQuad: function (t) {
- return t * t;
- },
- easeOutQuad: function (t) {
- return -1 * t * (t - 2);
- },
- easeInOutQuad: function (t) {
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * t * t;
- }
- return -1 / 2 * ((--t) * (t - 2) - 1);
- },
- easeInCubic: function (t) {
- return t * t * t;
- },
- easeOutCubic: function (t) {
- return 1 * ((t = t / 1 - 1) * t * t + 1);
- },
- easeInOutCubic: function (t) {
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * t * t * t;
- }
- return 1 / 2 * ((t -= 2) * t * t + 2);
- },
- easeInQuart: function (t) {
- return t * t * t * t;
- },
- easeOutQuart: function (t) {
- return -1 * ((t = t / 1 - 1) * t * t * t - 1);
- },
- easeInOutQuart: function (t) {
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * t * t * t * t;
- }
- return -1 / 2 * ((t -= 2) * t * t * t - 2);
- },
- easeInQuint: function (t) {
- return 1 * (t /= 1) * t * t * t * t;
- },
- easeOutQuint: function (t) {
- return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
- },
- easeInOutQuint: function (t) {
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * t * t * t * t * t;
- }
- return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
- },
- easeInSine: function (t) {
- return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
- },
- easeOutSine: function (t) {
- return 1 * Math.sin(t / 1 * (Math.PI / 2));
- },
- easeInOutSine: function (t) {
- return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
- },
- easeInExpo: function (t) {
- return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
- },
- easeOutExpo: function (t) {
- return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
- },
- easeInOutExpo: function (t) {
- if (t === 0) {
- return 0;
- }
- if (t === 1) {
- return 1;
- }
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * Math.pow(2, 10 * (t - 1));
- }
- return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
- },
- easeInCirc: function (t) {
- if (t >= 1) {
- return t;
- }
- return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
- },
- easeOutCirc: function (t) {
- return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
- },
- easeInOutCirc: function (t) {
- if ((t /= 1 / 2) < 1) {
- return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
- }
- return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
- },
- easeInElastic: function (t) {
- var s = 1.70158;
- var p = 0;
- var a = 1;
- if (t === 0) {
- return 0;
- }
- if ((t /= 1) == 1) {
- return 1;
- }
- if (!p) {
- p = 1 * 0.3;
- }
- if (a < Math.abs(1)) {
- a = 1;
- s = p / 4;
- } else {
- s = p / (2 * Math.PI) * Math.asin(1 / a);
- }
- return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
- },
- easeOutElastic: function (t) {
- var s = 1.70158;
- var p = 0;
- var a = 1;
- if (t === 0) {
- return 0;
- }
- if ((t /= 1) == 1) {
- return 1;
- }
- if (!p) {
- p = 1 * 0.3;
- }
- if (a < Math.abs(1)) {
- a = 1;
- s = p / 4;
- } else {
- s = p / (2 * Math.PI) * Math.asin(1 / a);
- }
- return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
- },
- easeInOutElastic: function (t) {
- var s = 1.70158;
- var p = 0;
- var a = 1;
- if (t === 0) {
- return 0;
- }
- if ((t /= 1 / 2) == 2) {
- return 1;
- }
- if (!p) {
- p = 1 * (0.3 * 1.5);
- }
- if (a < Math.abs(1)) {
- a = 1;
- s = p / 4;
- } else {
- s = p / (2 * Math.PI) * Math.asin(1 / a);
- }
- if (t < 1) {
- return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
- }
- return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
- },
- easeInBack: function (t) {
- var s = 1.70158;
- return 1 * (t /= 1) * t * ((s + 1) * t - s);
- },
- easeOutBack: function (t) {
- var s = 1.70158;
- return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
- },
- easeInOutBack: function (t) {
- var s = 1.70158;
- if ((t /= 1 / 2) < 1) {
- return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
- }
- return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
- },
- easeInBounce: function (t) {
- return 1 - easingEffects.easeOutBounce(1 - t);
- },
- easeOutBounce: function (t) {
- if ((t /= 1) < (1 / 2.75)) {
- return 1 * (7.5625 * t * t);
- } else if (t < (2 / 2.75)) {
- return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
- } else if (t < (2.5 / 2.75)) {
- return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
- } else {
- return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
- }
- },
- easeInOutBounce: function (t) {
- if (t < 1 / 2) {
- return easingEffects.easeInBounce(t * 2) * 0.5;
- }
- return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
- }
- };
- //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
- helpers.requestAnimFrame = (function () {
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function (callback) {
- return window.setTimeout(callback, 1000 / 60);
- };
- })();
- helpers.cancelAnimFrame = (function () {
- return window.cancelAnimationFrame ||
- window.webkitCancelAnimationFrame ||
- window.mozCancelAnimationFrame ||
- window.oCancelAnimationFrame ||
- window.msCancelAnimationFrame ||
- function (callback) {
- return window.clearTimeout(callback, 1000 / 60);
- };
- })();
- //-- DOM methods
- helpers.getRelativePosition = function (evt, chart) {
- var mouseX, mouseY;
- var e = evt.originalEvent || evt,
- canvas = evt.currentTarget || evt.srcElement,
- boundingRect = canvas.getBoundingClientRect();
- if (e.touches && e.touches.length > 0) {
- mouseX = e.touches[0].clientX;
- mouseY = e.touches[0].clientY;
- } else {
- mouseX = e.clientX;
- mouseY = e.clientY;
- }
- // Scale mouse coordinates into canvas coordinates
- // by following the pattern laid out by 'jerryj' in the comments of
- // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
- // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
- // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
- mouseX = Math.round((mouseX - boundingRect.left) / (boundingRect.right - boundingRect.left) * canvas.width / chart.currentDevicePixelRatio);
- mouseY = Math.round((mouseY - boundingRect.top) / (boundingRect.bottom - boundingRect.top) * canvas.height / chart.currentDevicePixelRatio);
- return {
- x: mouseX,
- y: mouseY
- };
- };
- helpers.addEvent = function (node, eventType, method) {
- if (node.addEventListener) {
- node.addEventListener(eventType, method);
- } else if (node.attachEvent) {
- node.attachEvent("on" + eventType, method);
- } else {
- node["on" + eventType] = method;
- }
- };
- helpers.removeEvent = function (node, eventType, handler) {
- if (node.removeEventListener) {
- node.removeEventListener(eventType, handler, false);
- } else if (node.detachEvent) {
- node.detachEvent("on" + eventType, handler);
- } else {
- node["on" + eventType] = helpers.noop;
- }
- };
- helpers.bindEvents = function (chartInstance, arrayOfEvents, handler) {
- // Create the events object if it's not already present
- if (!chartInstance.events) chartInstance.events = {};
- helpers.each(arrayOfEvents, function (eventName) {
- chartInstance.events[eventName] = function () {
- handler.apply(chartInstance, arguments);
- };
- helpers.addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]);
- });
- };
- helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
- helpers.each(arrayOfEvents, function (handler, eventName) {
- helpers.removeEvent(chartInstance.chart.canvas, eventName, handler);
- });
- };
- helpers.getConstraintWidth = function (domNode) { // returns Number or undefined if no constraint
- var constrainedWidth;
- var constrainedWNode = document.defaultView.getComputedStyle(domNode)['max-width'];
- var constrainedWContainer = document.defaultView.getComputedStyle(domNode.parentNode)['max-width'];
- var hasCWNode = constrainedWNode !== null && constrainedWNode !== "none";
- var hasCWContainer = constrainedWContainer !== null && constrainedWContainer !== "none";
- if (hasCWNode || hasCWContainer) {
- constrainedWidth = Math.min((hasCWNode ? parseInt(constrainedWNode, 10) : Number.POSITIVE_INFINITY), (hasCWContainer ? parseInt(constrainedWContainer, 10) : Number.POSITIVE_INFINITY));
- }
- return constrainedWidth;
- };
- // returns Number or undefined if no constraint
- helpers.getConstraintHeight = function (domNode) {
- var constrainedHeight;
- var constrainedHNode = document.defaultView.getComputedStyle(domNode)['max-height'];
- var constrainedHContainer = document.defaultView.getComputedStyle(domNode.parentNode)['max-height'];
- var hasCHNode = constrainedHNode !== null && constrainedHNode !== "none";
- var hasCHContainer = constrainedHContainer !== null && constrainedHContainer !== "none";
- if (constrainedHNode || constrainedHContainer) {
- constrainedHeight = Math.min((hasCHNode ? parseInt(constrainedHNode, 10) : Number.POSITIVE_INFINITY), (hasCHContainer ? parseInt(constrainedHContainer, 10) : Number.POSITIVE_INFINITY));
- }
- return constrainedHeight;
- };
- helpers.getMaximumWidth = function (domNode) {
- var container = domNode.parentNode;
- var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right'));
- var w = container.clientWidth - padding;
- var cw = helpers.getConstraintWidth(domNode);
- if (cw !== undefined) {
- w = Math.min(w, cw);
- }
- return w;
- };
- helpers.getMaximumHeight = function (domNode) {
- var container = domNode.parentNode;
- var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom'));
- var h = container.clientHeight - padding;
- var ch = helpers.getConstraintHeight(domNode);
- if (ch !== undefined) {
- h = Math.min(h, ch);
- }
- return h;
- };
- helpers.getStyle = function (el, property) {
- return el.currentStyle ?
- el.currentStyle[property] :
- document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
- };
- helpers.retinaScale = function (chart) {
- var ctx = chart.ctx;
- var width = chart.canvas.width;
- var height = chart.canvas.height;
- var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
- if (pixelRatio !== 1) {
- ctx.canvas.height = height * pixelRatio;
- ctx.canvas.width = width * pixelRatio;
- ctx.scale(pixelRatio, pixelRatio);
- ctx.canvas.style.width = width + 'px';
- ctx.canvas.style.height = height + 'px';
- // Store the device pixel ratio so that we can go backwards in `destroy`.
- // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
- // when destroy is called
- chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
- }
- };
- //-- Canvas methods
- helpers.clear = function (chart) {
- chart.ctx.clearRect(0, 0, chart.width, chart.height);
- };
- helpers.fontString = function (pixelSize, fontStyle, fontFamily) {
- return fontStyle + " " + pixelSize + "px " + fontFamily;
- };
- helpers.longestText = function (ctx, font, arrayOfStrings) {
- ctx.font = font;
- var longest = 0;
- helpers.each(arrayOfStrings, function (string) {
- var textWidth = ctx.measureText(string).width;
- longest = (textWidth > longest) ? textWidth : longest;
- });
- return longest;
- };
- helpers.drawRoundedRectangle = function (ctx, x, y, width, height, radius) {
- ctx.beginPath();
- ctx.moveTo(x + radius, y);
- ctx.lineTo(x + width - radius, y);
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
- ctx.lineTo(x + width, y + height - radius);
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
- ctx.lineTo(x + radius, y + height);
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
- ctx.lineTo(x, y + radius);
- ctx.quadraticCurveTo(x, y, x + radius, y);
- ctx.closePath();
- };
- helpers.color = function (color) {
- if (!root.Color) {
- console.log('Color.js not found!');
- return color;
- }
- return root.Color(color);
- };
- helpers.addResizeListener = function (node, callback) {
- // Hide an iframe before the node
- var hiddenIframe = document.createElement('iframe');
- var hiddenIframeClass = 'chartjs-hidden-iframe';
- if (hiddenIframe.classlist) {
- // can use classlist
- hiddenIframe.classlist.add(hiddenIframeClass);
- } else {
- hiddenIframe.setAttribute('class', hiddenIframeClass);
- }
- // Set the style
- hiddenIframe.style.width = '100%';
- hiddenIframe.style.display = 'block';
- hiddenIframe.style.border = 0;
- hiddenIframe.style.height = 0;
- hiddenIframe.style.margin = 0;
- hiddenIframe.style.position = 'absolute';
- hiddenIframe.style.left = 0;
- hiddenIframe.style.right = 0;
- hiddenIframe.style.top = 0;
- hiddenIframe.style.bottom = 0;
- // Insert the iframe so that contentWindow is available
- node.insertBefore(hiddenIframe, node.firstChild);
- var timer = 0;
- (hiddenIframe.contentWindow || hiddenIframe).onresize = function () {
- if (callback) {
- callback();
- }
- };
- };
- helpers.removeResizeListener = function (node) {
- var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
- // Remove the resize detect iframe
- if (hiddenIframe) {
- hiddenIframe.parentNode.removeChild(hiddenIframe);
- }
- };
- helpers.isArray = function (obj) {
- if (!Array.isArray) {
- return Object.prototype.toString.call(arg) === '[object Array]';
- }
- return Array.isArray(obj);
- };
- helpers.isDatasetVisible = function (dataset) {
- return !dataset.hidden;
- };
- helpers.callCallback = function (fn, args, _tArg) {
- if (fn && typeof fn.call === 'function') {
- fn.apply(_tArg, args);
- }
- };
- }).call(this);
- (function () {
- "use strict";
- //Declare root variable - window in the browser, global on the server
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.elements = {};
- Chart.Element = function (configuration) {
- helpers.extend(this, configuration);
- this.initialize.apply(this, arguments);
- };
- helpers.extend(Chart.Element.prototype, {
- initialize: function () {
- },
- pivot: function () {
- if (!this._view) {
- this._view = helpers.clone(this._model);
- }
- this._start = helpers.clone(this._view);
- return this;
- },
- transition: function (ease) {
- if (!this._view) {
- this._view = helpers.clone(this._model);
- }
- if (!this._start) {
- this.pivot();
- }
- helpers.each(this._model, function (value, key) {
- if (key[0] === '_' || !this._model.hasOwnProperty(key)) {
- // Only non-underscored properties
- }
- // Init if doesn't exist
- else if (!this._view[key]) {
- if (typeof value === 'number' && isNaN(this._view[key]) === false) {
- this._view[key] = value * ease;
- } else {
- this._view[key] = value || null;
- }
- }
- // No unnecessary computations
- else if (this._model[key] === this._view[key]) {
- // It's the same! Woohoo!
- }
- // Color transitions if possible
- else if (typeof value === 'string') {
- try {
- var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease);
- this._view[key] = color.rgbString();
- } catch (err) {
- this._view[key] = value;
- }
- }
- // Number transitions
- else if (typeof value === 'number') {
- var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0;
- this._view[key] = ((this._model[key] - startVal) * ease) + startVal;
- }
- // Everything else
- else {
- this._view[key] = value;
- }
- }, this);
- if (ease === 1) {
- delete this._start;
- }
- return this;
- },
- tooltipPosition: function () {
- return {
- x: this._model.x,
- y: this._model.y
- };
- },
- hasValue: function () {
- return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
- }
- });
- Chart.Element.extend = helpers.inherits;
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.animation = {
- duration: 1000,
- easing: "easeOutQuart",
- onProgress: helpers.noop,
- onComplete: helpers.noop,
- };
- Chart.Animation = Chart.Element.extend({
- currentStep: null, // the current animation step
- numSteps: 60, // default number of steps
- easing: "", // the easing to use for this animation
- render: null, // render function used by the animation service
- onAnimationProgress: null, // user specified callback to fire on each step of the animation
- onAnimationComplete: null, // user specified callback to fire when the animation finishes
- });
- Chart.animationService = {
- frameDuration: 17,
- animations: [],
- dropFrames: 0,
- addAnimation: function (chartInstance, animationObject, duration, lazy) {
- if (!lazy) {
- chartInstance.animating = true;
- }
- for (var index = 0; index < this.animations.length; ++index) {
- if (this.animations[index].chartInstance === chartInstance) {
- // replacing an in progress animation
- this.animations[index].animationObject = animationObject;
- return;
- }
- }
- this.animations.push({
- chartInstance: chartInstance,
- animationObject: animationObject
- });
- // If there are no animations queued, manually kickstart a digest, for lack of a better word
- if (this.animations.length == 1) {
- helpers.requestAnimFrame.call(window, this.digestWrapper);
- }
- },
- // Cancel the animation for a given chart instance
- cancelAnimation: function (chartInstance) {
- var index = helpers.findNextWhere(this.animations, function (animationWrapper) {
- return animationWrapper.chartInstance === chartInstance;
- });
- if (index) {
- this.animations.splice(index, 1);
- chartInstance.animating = false;
- }
- },
- // calls startDigest with the proper context
- digestWrapper: function () {
- Chart.animationService.startDigest.call(Chart.animationService);
- },
- startDigest: function () {
- var startTime = Date.now();
- var framesToDrop = 0;
- if (this.dropFrames > 1) {
- framesToDrop = Math.floor(this.dropFrames);
- this.dropFrames = this.dropFrames % 1;
- }
- for (var i = 0; i < this.animations.length; i++) {
- if (this.animations[i].animationObject.currentStep === null) {
- this.animations[i].animationObject.currentStep = 0;
- }
- this.animations[i].animationObject.currentStep += 1 + framesToDrop;
- if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
- this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
- }
- this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
- if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) {
- this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]);
- }
- if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) {
- if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) {
- this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]);
- }
- // executed the last frame. Remove the animation.
- this.animations[i].chartInstance.animating = false;
- this.animations.splice(i, 1);
- // Keep the index in place to offset the splice
- i--;
- }
- }
- var endTime = Date.now();
- var dropFrames = (endTime - startTime) / this.frameDuration;
- this.dropFrames += dropFrames;
- // Do we have more stuff to animate?
- if (this.animations.length > 0) {
- helpers.requestAnimFrame.call(window, this.digestWrapper);
- }
- }
- };
- }).call(this);
- (function () {
- "use strict";
- //Declare root variable - window in the browser, global on the server
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- //Create a dictionary of chart types, to allow for extension of existing types
- Chart.types = {};
- //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
- //Destroy method on the chart will remove the instance of the chart from this reference.
- Chart.instances = {};
- // Controllers available for dataset visualization eg. bar, line, slice, etc.
- Chart.controllers = {};
- // The main controller of a chart
- Chart.Controller = function (instance) {
- this.chart = instance;
- this.config = instance.config;
- this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
- this.id = helpers.uid();
- Object.defineProperty(this, 'data', {
- get: function () {
- return this.config.data;
- },
- });
- //Add the chart instance to the global namespace
- Chart.instances[this.id] = this;
- if (this.options.responsive) {
- // Silent resize before chart draws
- this.resize(true);
- }
- this.initialize.call(this);
- return this;
- };
- helpers.extend(Chart.Controller.prototype, {
- initialize: function initialize() {
- // TODO
- // If BeforeInit(this) doesn't return false, proceed
- this.bindEvents();
- // Make sure controllers are built first so that each dataset is bound to an axis before the scales
- // are built
- this.ensureScalesHaveIDs();
- this.buildOrUpdateControllers();
- this.buildScales();
- this.buildSurroundingItems();
- this.updateLayout();
- this.resetElements();
- this.initToolTip();
- this.draw();
- this.update();
- // TODO
- // If AfterInit(this) doesn't return false, proceed
- return this;
- },
- clear: function clear() {
- helpers.clear(this.chart);
- return this;
- },
- stop: function stop() {
- // Stops any current animation loop occuring
- Chart.animationService.cancelAnimation(this);
- return this;
- },
- resize: function resize(silent) {
- var canvas = this.chart.canvas;
- var newWidth = helpers.getMaximumWidth(this.chart.canvas);
- var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas);
- var sizeChanged = this.chart.width !== newWidth || this.chart.height !== newHeight;
- canvas.width = this.chart.width = newWidth;
- canvas.height = this.chart.height = newHeight;
- helpers.retinaScale(this.chart);
- if (!silent && sizeChanged) {
- this.stop();
- this.update(this.options.responsiveAnimationDuration);
- }
- return this;
- },
- ensureScalesHaveIDs: function ensureScalesHaveIDs() {
- var defaultXAxisID = 'x-axis-';
- var defaultYAxisID = 'y-axis-';
- if (this.options.scales) {
- if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
- helpers.each(this.options.scales.xAxes, function (xAxisOptions, index) {
- xAxisOptions.id = xAxisOptions.id || (defaultXAxisID + index);
- }, this);
- }
- if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
- // Build the y axes
- helpers.each(this.options.scales.yAxes, function (yAxisOptions, index) {
- yAxisOptions.id = yAxisOptions.id || (defaultYAxisID + index);
- }, this);
- }
- }
- },
- buildScales: function buildScales() {
- // Map of scale ID to scale object so we can lookup later
- this.scales = {};
- // Build the x axes
- if (this.options.scales) {
- if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
- helpers.each(this.options.scales.xAxes, function (xAxisOptions, index) {
- var ScaleClass = Chart.scaleService.getScaleConstructor(xAxisOptions.type);
- var scale = new ScaleClass({
- ctx: this.chart.ctx,
- options: xAxisOptions,
- chart: this,
- id: xAxisOptions.id,
- });
- this.scales[scale.id] = scale;
- }, this);
- }
- if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
- // Build the y axes
- helpers.each(this.options.scales.yAxes, function (yAxisOptions, index) {
- var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type);
- var scale = new ScaleClass({
- ctx: this.chart.ctx,
- options: yAxisOptions,
- chart: this,
- id: yAxisOptions.id,
- });
- this.scales[scale.id] = scale;
- }, this);
- }
- }
- if (this.options.scale) {
- // Build radial axes
- var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scale.type);
- var scale = new ScaleClass({
- ctx: this.chart.ctx,
- options: this.options.scale,
- chart: this,
- });
- this.scale = scale;
- this.scales.radialScale = scale;
- }
- Chart.scaleService.addScalesToLayout(this);
- },
- buildSurroundingItems: function () {
- if (this.options.title) {
- this.titleBlock = new Chart.Title({
- ctx: this.chart.ctx,
- options: this.options.title,
- chart: this
- });
- Chart.layoutService.addBox(this, this.titleBlock);
- }
- if (this.options.legend) {
- this.legend = new Chart.Legend({
- ctx: this.chart.ctx,
- options: this.options.legend,
- chart: this,
- });
- Chart.layoutService.addBox(this, this.legend);
- }
- },
- updateLayout: function () {
- Chart.layoutService.update(this, this.chart.width, this.chart.height);
- },
- buildOrUpdateControllers: function buildOrUpdateControllers(resetNewControllers) {
- var types = [];
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- if (!dataset.type) {
- dataset.type = this.config.type;
- }
- var type = dataset.type;
- types.push(type);
- if (dataset.controller) {
- dataset.controller.updateIndex(datasetIndex);
- } else {
- dataset.controller = new Chart.controllers[type](this, datasetIndex);
- if (resetNewControllers) {
- dataset.controller.reset();
- }
- }
- }, this);
- if (types.length > 1) {
- for (var i = 1; i < types.length; i++) {
- if (types[i] != types[i - 1]) {
- this.isCombo = true;
- break;
- }
- }
- }
- },
- resetElements: function resetElements() {
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- dataset.controller.reset();
- }, this);
- },
- update: function update(animationDuration, lazy) {
- // In case the entire data object changed
- this.tooltip._data = this.data;
- // Make sure dataset controllers are updated and new controllers are reset
- this.buildOrUpdateControllers(true);
- Chart.layoutService.update(this, this.chart.width, this.chart.height);
- // Make sure all dataset controllers have correct meta data counts
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- dataset.controller.buildOrUpdateElements();
- }, this);
- // This will loop through any data and do the appropriate element update for the type
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- dataset.controller.update();
- }, this);
- this.render(animationDuration, lazy);
- },
- render: function render(duration, lazy) {
- if (this.options.animation && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration == 'undefined' && this.options.animation.duration !== 0))) {
- var animation = new Chart.Animation();
- animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps
- animation.easing = this.options.animation.easing;
- // render function
- animation.render = function (chartInstance, animationObject) {
- var easingFunction = helpers.easingEffects[animationObject.easing];
- var stepDecimal = animationObject.currentStep / animationObject.numSteps;
- var easeDecimal = easingFunction(stepDecimal);
- chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
- };
- // user events
- animation.onAnimationProgress = this.options.animation.onProgress;
- animation.onAnimationComplete = this.options.animation.onComplete;
- Chart.animationService.addAnimation(this, animation, duration, lazy);
- } else {
- this.draw();
- if (this.options.animation && this.options.animation.onComplete && this.options.animation.onComplete.call) {
- this.options.animation.onComplete.call(this);
- }
- }
- return this;
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- this.clear();
- // Draw all the scales
- helpers.each(this.boxes, function (box) {
- box.draw(this.chartArea);
- }, this);
- if (this.scale) {
- this.scale.draw();
- }
- // Draw each dataset via its respective controller (reversed to support proper line stacking)
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- if (helpers.isDatasetVisible(dataset)) {
- dataset.controller.draw(ease);
- }
- }, this);
- // Finally draw the tooltip
- this.tooltip.transition(easingDecimal).draw();
- },
- // Get the single element that was clicked on
- // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
- getElementAtEvent: function (e) {
- var eventPosition = helpers.getRelativePosition(e, this.chart);
- var elementsArray = [];
- helpers.each(this.data.datasets, function (dataset, datasetIndex) {
- if (helpers.isDatasetVisible(dataset)) {
- helpers.each(dataset.metaData, function (element, index) {
- if (element.inRange(eventPosition.x, eventPosition.y)) {
- elementsArray.push(element);
- return elementsArray;
- }
- }, this);
- }
- }, this);
- return elementsArray;
- },
- getElementsAtEvent: function (e) {
- var eventPosition = helpers.getRelativePosition(e, this.chart);
- var elementsArray = [];
- var found = (function () {
- for (var i = 0; i < this.data.datasets.length; i++) {
- if (helpers.isDatasetVisible(this.data.datasets[i])) {
- for (var j = 0; j < this.data.datasets[i].metaData.length; j++) {
- if (this.data.datasets[i].metaData[j].inRange(eventPosition.x, eventPosition.y)) {
- return this.data.datasets[i].metaData[j];
- }
- }
- }
- }
- }).call(this);
- if (!found) {
- return elementsArray;
- }
- helpers.each(this.data.datasets, function (dataset, dsIndex) {
- if (helpers.isDatasetVisible(dataset)) {
- elementsArray.push(dataset.metaData[found._index]);
- }
- }, this);
- return elementsArray;
- },
- getDatasetAtEvent: function (e) {
- var elementsArray = this.getElementAtEvent(e);
- if (elementsArray.length > 0) {
- elementsArray = this.data.datasets[elementsArray[0]._datasetIndex].metaData;
- }
- return elementsArray;
- },
- generateLegend: function generateLegend() {
- return this.options.legendCallback(this);
- },
- destroy: function destroy() {
- this.clear();
- helpers.unbindEvents(this, this.events);
- helpers.removeResizeListener(this.chart.canvas.parentNode);
- // Reset canvas height/width attributes
- var canvas = this.chart.canvas;
- canvas.width = this.chart.width;
- canvas.height = this.chart.height;
- // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
- if (this.chart.originalDevicePixelRatio !== undefined) {
- this.chart.ctx.scale(1 / this.chart.originalDevicePixelRatio, 1 / this.chart.originalDevicePixelRatio);
- }
- // Reset to the old style since it may have been changed by the device pixel ratio changes
- canvas.style.width = this.chart.originalCanvasStyleWidth;
- canvas.style.height = this.chart.originalCanvasStyleHeight;
- delete Chart.instances[this.id];
- },
- toBase64Image: function toBase64Image() {
- return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
- },
- initToolTip: function initToolTip() {
- this.tooltip = new Chart.Tooltip({
- _chart: this.chart,
- _chartInstance: this,
- _data: this.data,
- _options: this.options,
- }, this);
- },
- bindEvents: function bindEvents() {
- helpers.bindEvents(this, this.options.events, function (evt) {
- this.eventHandler(evt);
- });
- },
- eventHandler: function eventHandler(e) {
- this.lastActive = this.lastActive || [];
- this.lastTooltipActive = this.lastTooltipActive || [];
- // Find Active Elements for hover and tooltips
- if (e.type == 'mouseout') {
- this.active = [];
- this.tooltipActive = [];
- } else {
- var _this = this;
- var getItemsForMode = function (mode) {
- switch (mode) {
- case 'single':
- return _this.getElementAtEvent(e);
- case 'label':
- return _this.getElementsAtEvent(e);
- case 'dataset':
- return _this.getDatasetAtEvent(e);
- default:
- return e;
- }
- };
- this.active = getItemsForMode(this.options.hover.mode);
- this.tooltipActive = getItemsForMode(this.options.tooltips.mode);
- }
- // On Hover hook
- if (this.options.hover.onHover) {
- this.options.hover.onHover.call(this, this.active);
- }
- if (e.type == 'mouseup' || e.type == 'click') {
- if (this.options.onClick) {
- this.options.onClick.call(this, e, this.active);
- }
- if (this.legend && this.legend.handleEvent) {
- this.legend.handleEvent(e);
- }
- }
- var dataset;
- var index;
- // Remove styling for last active (even if it may still be active)
- if (this.lastActive.length) {
- switch (this.options.hover.mode) {
- case 'single':
- this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0], this.lastActive[0]._datasetIndex, this.lastActive[0]._index);
- break;
- case 'label':
- case 'dataset':
- for (var i = 0; i < this.lastActive.length; i++) {
- if (this.lastActive[i])
- this.data.datasets[this.lastActive[i]._datasetIndex].controller.removeHoverStyle(this.lastActive[i], this.lastActive[i]._datasetIndex, this.lastActive[i]._index);
- }
- break;
- default:
- // Don't change anything
- }
- }
- // Built in hover styling
- if (this.active.length && this.options.hover.mode) {
- switch (this.options.hover.mode) {
- case 'single':
- this.data.datasets[this.active[0]._datasetIndex].controller.setHoverStyle(this.active[0]);
- break;
- case 'label':
- case 'dataset':
- for (var j = 0; j < this.active.length; j++) {
- if (this.active[j])
- this.data.datasets[this.active[j]._datasetIndex].controller.setHoverStyle(this.active[j]);
- }
- break;
- default:
- // Don't change anything
- }
- }
- // Built in Tooltips
- if (this.options.tooltips.enabled || this.options.tooltips.custom) {
- // The usual updates
- this.tooltip.initialize();
- this.tooltip._active = this.tooltipActive;
- this.tooltip.update();
- }
- // Hover animations
- this.tooltip.pivot();
- if (!this.animating) {
- var changed;
- helpers.each(this.active, function (element, index) {
- if (element !== this.lastActive[index]) {
- changed = true;
- }
- }, this);
- helpers.each(this.tooltipActive, function (element, index) {
- if (element !== this.lastTooltipActive[index]) {
- changed = true;
- }
- }, this);
- // If entering, leaving, or changing elements, animate the change via pivot
- if ((this.lastActive.length !== this.active.length) ||
- (this.lastTooltipActive.length !== this.tooltipActive.length) ||
- changed) {
- this.stop();
- if (this.options.tooltips.enabled || this.options.tooltips.custom) {
- this.tooltip.update(true);
- }
- // We only need to render at this point. Updating will cause scales to be recomputed generating flicker & using more
- // memory than necessary.
- this.render(this.options.hover.animationDuration, true);
- }
- }
- // Remember Last Actives
- this.lastActive = this.active;
- this.lastTooltipActive = this.tooltipActive;
- return this;
- },
- });
- }).call(this);
- (function () {
- "use strict";
- //Declare root variable - window in the browser, global on the server
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- // Base class for all dataset controllers (line, bar, etc)
- Chart.DatasetController = function (chart, datasetIndex) {
- this.initialize.call(this, chart, datasetIndex);
- };
- helpers.extend(Chart.DatasetController.prototype, {
- initialize: function (chart, datasetIndex) {
- this.chart = chart;
- this.index = datasetIndex;
- this.linkScales();
- this.addElements();
- },
- updateIndex: function (datasetIndex) {
- this.index = datasetIndex;
- },
- linkScales: function () {
- if (!this.getDataset().xAxisID) {
- this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id;
- }
- if (!this.getDataset().yAxisID) {
- this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id;
- }
- },
- getDataset: function () {
- return this.chart.data.datasets[this.index];
- },
- getScaleForId: function (scaleID) {
- return this.chart.scales[scaleID];
- },
- reset: function () {
- this.update(true);
- },
- buildOrUpdateElements: function buildOrUpdateElements() {
- // Handle the number of data points changing
- var numData = this.getDataset().data.length;
- var numMetaData = this.getDataset().metaData.length;
- // Make sure that we handle number of datapoints changing
- if (numData < numMetaData) {
- // Remove excess bars for data points that have been removed
- this.getDataset().metaData.splice(numData, numMetaData - numData);
- } else if (numData > numMetaData) {
- // Add new elements
- for (var index = numMetaData; index < numData; ++index) {
- this.addElementAndReset(index);
- }
- }
- },
- // Controllers should implement the following
- addElements: helpers.noop,
- addElementAndReset: helpers.noop,
- draw: helpers.noop,
- removeHoverStyle: helpers.noop,
- setHoverStyle: helpers.noop,
- update: helpers.noop,
- });
- Chart.DatasetController.extend = helpers.inherits;
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- // The layout service is ver self explanatory. It's responsible for the layout within a chart.
- // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
- // It is this service's responsibility of carrying out that layout.
- Chart.layoutService = {
- defaults: {},
- // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
- addBox: function (chartInstance, box) {
- if (!chartInstance.boxes) {
- chartInstance.boxes = [];
- }
- chartInstance.boxes.push(box);
- },
- removeBox: function (chartInstance, box) {
- if (!chartInstance.boxes) {
- return;
- }
- chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
- },
- // The most important function
- update: function (chartInstance, width, height) {
- if (!chartInstance) {
- return;
- }
- var xPadding = width > 30 ? 5 : 2;
- var yPadding = height > 30 ? 5 : 2;
- var leftBoxes = helpers.where(chartInstance.boxes, function (box) {
- return box.options.position == "left";
- });
- var rightBoxes = helpers.where(chartInstance.boxes, function (box) {
- return box.options.position == "right";
- });
- var topBoxes = helpers.where(chartInstance.boxes, function (box) {
- return box.options.position == "top";
- });
- var bottomBoxes = helpers.where(chartInstance.boxes, function (box) {
- return box.options.position == "bottom";
- });
- // Boxes that overlay the chartarea such as the radialLinear scale
- var chartAreaBoxes = helpers.where(chartInstance.boxes, function (box) {
- return box.options.position == "chartArea";
- });
- function fullWidthSorter(a, b) {
- }
- // Ensure that full width boxes are at the very top / bottom
- topBoxes.sort(function (a, b) {
- return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
- });
- bottomBoxes.sort(function (a, b) {
- return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
- });
- // Essentially we now have any number of boxes on each of the 4 sides.
- // Our canvas looks like the following.
- // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
- // B1 is the bottom axis
- // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
- // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
- // an error will be thrown.
- //
- // |----------------------------------------------------|
- // | T1 (Full Width) |
- // |----------------------------------------------------|
- // | | | T2 | |
- // | |----|-------------------------------------|----|
- // | | | C1 | | C2 | |
- // | | |----| |----| |
- // | | | | |
- // | L1 | L2 | ChartArea (C0) | R1 |
- // | | | | |
- // | | |----| |----| |
- // | | | C3 | | C4 | |
- // | |----|-------------------------------------|----|
- // | | | B1 | |
- // |----------------------------------------------------|
- // | B2 (Full Width) |
- // |----------------------------------------------------|
- //
- // What we do to find the best sizing, we do the following
- // 1. Determine the minimum size of the chart area.
- // 2. Split the remaining width equally between each vertical axis
- // 3. Split the remaining height equally between each horizontal axis
- // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
- // 5. Adjust the sizes of each axis based on it's minimum reported size.
- // 6. Refit each axis
- // 7. Position each axis in the final location
- // 8. Tell the chart the final location of the chart area
- // 9. Tell any axes that overlay the chart area the positions of the chart area
- // Step 1
- var chartWidth = width - (2 * xPadding);
- var chartHeight = height - (2 * yPadding);
- var chartAreaWidth = chartWidth / 2; // min 50%
- var chartAreaHeight = chartHeight / 2; // min 50%
- // Step 2
- var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
- // Step 3
- var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
- // Step 4
- var maxChartAreaWidth = chartWidth;
- var maxChartAreaHeight = chartHeight;
- var minBoxSizes = [];
- helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
- function getMinimumBoxSize(box) {
- var minSize;
- var isHorizontal = box.isHorizontal();
- if (isHorizontal) {
- minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
- maxChartAreaHeight -= minSize.height;
- } else {
- minSize = box.update(verticalBoxWidth, chartAreaHeight);
- maxChartAreaWidth -= minSize.width;
- }
- minBoxSizes.push({
- horizontal: isHorizontal,
- minSize: minSize,
- box: box,
- });
- }
- // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
- // be if the axes are drawn at their minimum sizes.
- // Steps 5 & 6
- var totalLeftBoxesWidth = xPadding;
- var totalRightBoxesWidth = xPadding;
- var totalTopBoxesHeight = yPadding;
- var totalBottomBoxesHeight = yPadding;
- // Update, and calculate the left and right margins for the horizontal boxes
- helpers.each(leftBoxes.concat(rightBoxes), fitBox);
- helpers.each(leftBoxes, function (box) {
- totalLeftBoxesWidth += box.width;
- });
- helpers.each(rightBoxes, function (box) {
- totalRightBoxesWidth += box.width;
- });
- // Set the Left and Right margins for the horizontal boxes
- helpers.each(topBoxes.concat(bottomBoxes), fitBox);
- // Function to fit a box
- function fitBox(box) {
- var minBoxSize = helpers.findNextWhere(minBoxSizes, function (minBoxSize) {
- return minBoxSize.box === box;
- });
- if (minBoxSize) {
- if (box.isHorizontal()) {
- var scaleMargin = {
- left: totalLeftBoxesWidth,
- right: totalRightBoxesWidth,
- top: 0,
- bottom: 0,
- };
- // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
- // on the margin. Sometimes they need to increase in size slightly
- box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
- } else {
- box.update(minBoxSize.minSize.width, maxChartAreaHeight);
- }
- }
- }
- // Figure out how much margin is on the top and bottom of the vertical boxes
- helpers.each(topBoxes, function (box) {
- totalTopBoxesHeight += box.height;
- });
- helpers.each(bottomBoxes, function (box) {
- totalBottomBoxesHeight += box.height;
- });
- // Let the left layout know the final margin
- helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
- function finalFitVerticalBox(box) {
- var minBoxSize = helpers.findNextWhere(minBoxSizes, function (minBoxSize) {
- return minBoxSize.box === box;
- });
- var scaleMargin = {
- left: 0,
- right: 0,
- top: totalTopBoxesHeight,
- bottom: totalBottomBoxesHeight
- };
- if (minBoxSize) {
- box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
- }
- }
- // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
- totalLeftBoxesWidth = xPadding;
- totalRightBoxesWidth = xPadding;
- totalTopBoxesHeight = yPadding;
- totalBottomBoxesHeight = yPadding;
- helpers.each(leftBoxes, function (box) {
- totalLeftBoxesWidth += box.width;
- });
- helpers.each(rightBoxes, function (box) {
- totalRightBoxesWidth += box.width;
- });
- helpers.each(topBoxes, function (box) {
- totalTopBoxesHeight += box.height;
- });
- helpers.each(bottomBoxes, function (box) {
- totalBottomBoxesHeight += box.height;
- });
- // Figure out if our chart area changed. This would occur if the dataset layout label rotation
- // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
- // without calling `fit` again
- var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
- var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
- if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
- helpers.each(leftBoxes, function (box) {
- box.height = newMaxChartAreaHeight;
- });
- helpers.each(rightBoxes, function (box) {
- box.height = newMaxChartAreaHeight;
- });
- helpers.each(topBoxes, function (box) {
- box.width = newMaxChartAreaWidth;
- });
- helpers.each(bottomBoxes, function (box) {
- box.width = newMaxChartAreaWidth;
- });
- maxChartAreaHeight = newMaxChartAreaHeight;
- maxChartAreaWidth = newMaxChartAreaWidth;
- }
- // Step 7 - Position the boxes
- var left = xPadding;
- var top = yPadding;
- var right = 0;
- var bottom = 0;
- helpers.each(leftBoxes.concat(topBoxes), placeBox);
- // Account for chart width and height
- left += maxChartAreaWidth;
- top += maxChartAreaHeight;
- helpers.each(rightBoxes, placeBox);
- helpers.each(bottomBoxes, placeBox);
- function placeBox(box) {
- if (box.isHorizontal()) {
- box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
- box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
- box.top = top;
- box.bottom = top + box.height;
- // Move to next point
- top = box.bottom;
- } else {
- box.left = left;
- box.right = left + box.width;
- box.top = totalTopBoxesHeight;
- box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
- // Move to next point
- left = box.right;
- }
- }
- // Step 8
- chartInstance.chartArea = {
- left: totalLeftBoxesWidth,
- top: totalTopBoxesHeight,
- right: totalLeftBoxesWidth + maxChartAreaWidth,
- bottom: totalTopBoxesHeight + maxChartAreaHeight,
- };
- // Step 9
- helpers.each(chartAreaBoxes, function (box) {
- box.left = chartInstance.chartArea.left;
- box.top = chartInstance.chartArea.top;
- box.right = chartInstance.chartArea.right;
- box.bottom = chartInstance.chartArea.bottom;
- box.update(maxChartAreaWidth, maxChartAreaHeight);
- });
- }
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.legend = {
- display: true,
- position: 'top',
- fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
- // a callback that will handle
- onClick: function (e, legendItem) {
- var dataset = this.chart.data.datasets[legendItem.datasetIndex];
- dataset.hidden = !dataset.hidden;
- // We hid a dataset ... rerender the chart
- this.chart.update();
- },
- labels: {
- boxWidth: 40,
- fontSize: 14,
- fontStyle: "normal",
- fontColor: "#333",
- fontFamily: "Roboto",
- padding: 20,
- // Generates labels shown in the legend
- // Valid properties to return:
- // text : text to display
- // fillStyle : fill of coloured box
- // strokeStyle: stroke of coloured box
- // hidden : if this legend item refers to a hidden item
- // lineCap : cap style for line
- // lineDash
- // lineDashOffset :
- // lineJoin :
- // lineWidth :
- generateLabels: function (data) {
- return data.datasets.map(function (dataset, i) {
- return {
- text: dataset.label,
- fillStyle: dataset.backgroundColor,
- hidden: dataset.hidden,
- lineCap: dataset.borderCapStyle,
- lineDash: dataset.borderDash,
- lineDashOffset: dataset.borderDashOffset,
- lineJoin: dataset.borderJoinStyle,
- lineWidth: dataset.borderWidth,
- strokeStyle: dataset.borderColor,
- // Below is extra data used for toggling the datasets
- datasetIndex: i
- };
- }, this);
- }
- },
- };
- Chart.Legend = Chart.Element.extend({
- initialize: function (config) {
- helpers.extend(this, config);
- // Contains hit boxes for each dataset (in dataset order)
- this.legendHitBoxes = [];
- // Are we in doughnut mode which has a different data type
- this.doughnutMode = false;
- },
- // These methods are ordered by lifecyle. Utilities then follow.
- // Any function defined here is inherited by all legend types.
- // Any function can be extended by the legend type
- beforeUpdate: helpers.noop,
- update: function (maxWidth, maxHeight, margins) {
- // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
- this.beforeUpdate();
- // Absorb the master measurements
- this.maxWidth = maxWidth;
- this.maxHeight = maxHeight;
- this.margins = margins;
- // Dimensions
- this.beforeSetDimensions();
- this.setDimensions();
- this.afterSetDimensions();
- // Labels
- this.beforeBuildLabels();
- this.buildLabels();
- this.afterBuildLabels();
- // Fit
- this.beforeFit();
- this.fit();
- this.afterFit();
- //
- this.afterUpdate();
- return this.minSize;
- },
- afterUpdate: helpers.noop,
- //
- beforeSetDimensions: helpers.noop,
- setDimensions: function () {
- // Set the unconstrained dimension before label rotation
- if (this.isHorizontal()) {
- // Reset position before calculating rotation
- this.width = this.maxWidth;
- this.left = 0;
- this.right = this.width;
- } else {
- this.height = this.maxHeight;
- // Reset position before calculating rotation
- this.top = 0;
- this.bottom = this.height;
- }
- // Reset padding
- this.paddingLeft = 0;
- this.paddingTop = 0;
- this.paddingRight = 0;
- this.paddingBottom = 0;
- // Reset minSize
- this.minSize = {
- width: 0,
- height: 0,
- };
- },
- afterSetDimensions: helpers.noop,
- //
- beforeBuildLabels: helpers.noop,
- buildLabels: function () {
- this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data);
- },
- afterBuildLabels: helpers.noop,
- //
- beforeFit: helpers.noop,
- fit: function () {
- var ctx = this.ctx;
- var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
- // Reset hit boxes
- this.legendHitBoxes = [];
- // Width
- if (this.isHorizontal()) {
- this.minSize.width = this.maxWidth; // fill all the width
- } else {
- this.minSize.width = this.options.display ? 10 : 0;
- }
- // height
- if (this.isHorizontal()) {
- this.minSize.height = this.options.display ? 10 : 0;
- } else {
- this.minSize.height = this.maxHeight; // fill all the height
- }
- // Increase sizes here
- if (this.options.display) {
- if (this.isHorizontal()) {
- // Labels
- // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
- this.lineWidths = [0];
- var totalHeight = this.legendItems.length ? this.options.labels.fontSize + (this.options.labels.padding) : 0;
- ctx.textAlign = "left";
- ctx.textBaseline = 'top';
- ctx.font = labelFont;
- helpers.each(this.legendItems, function (legendItem, i) {
- var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + ctx.measureText(legendItem.text).width;
- if (this.lineWidths[this.lineWidths.length - 1] + width >= this.width) {
- totalHeight += this.options.labels.fontSize + (this.options.labels.padding);
- this.lineWidths[this.lineWidths.length] = this.left;
- }
- // Store the hitbox width and height here. Final position will be updated in `draw`
- this.legendHitBoxes[i] = {
- left: 0,
- top: 0,
- width: width,
- height: this.options.labels.fontSize,
- };
- this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding;
- }, this);
- this.minSize.height += totalHeight;
- } else {
- // TODO vertical
- }
- }
- this.width = this.minSize.width;
- this.height = this.minSize.height;
- },
- afterFit: helpers.noop,
- // Shared Methods
- isHorizontal: function () {
- return this.options.position == "top" || this.options.position == "bottom";
- },
- // Actualy draw the legend on the canvas
- draw: function () {
- if (this.options.display) {
- var ctx = this.ctx;
- var cursor = {
- x: this.left + ((this.width - this.lineWidths[0]) / 2),
- y: this.top + this.options.labels.padding,
- line: 0,
- };
- var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
- // Horizontal
- if (this.isHorizontal()) {
- // Labels
- ctx.textAlign = "left";
- ctx.textBaseline = 'top';
- ctx.lineWidth = 0.5;
- ctx.strokeStyle = this.options.labels.fontColor; // for strikethrough effect
- ctx.fillStyle = this.options.labels.fontColor; // render in correct colour
- ctx.font = labelFont;
- helpers.each(this.legendItems, function (legendItem, i) {
- var textWidth = ctx.measureText(legendItem.text).width;
- var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + textWidth;
- if (cursor.x + width >= this.width) {
- cursor.y += this.options.labels.fontSize + (this.options.labels.padding);
- cursor.line++;
- cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2);
- }
- // Set the ctx for the box
- ctx.save();
- var itemOrDefault = function (item, defaulVal) {
- return item !== undefined ? item : defaulVal;
- };
- ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor);
- ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle);
- ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset);
- ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle);
- ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth);
- ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor);
- if (ctx.setLineDash) {
- // IE 9 and 10 do not support line dash
- ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash));
- }
- // Draw the box
- ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
- ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
- ctx.restore();
- this.legendHitBoxes[i].left = cursor.x;
- this.legendHitBoxes[i].top = cursor.y;
- // Fill the actual label
- ctx.fillText(legendItem.text, this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y);
- if (legendItem.hidden) {
- // Strikethrough the text if hidden
- ctx.beginPath();
- ctx.lineWidth = 2;
- ctx.moveTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y + (this.options.labels.fontSize / 2));
- ctx.lineTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x + textWidth, cursor.y + (this.options.labels.fontSize / 2));
- ctx.stroke();
- }
- cursor.x += width + (this.options.labels.padding);
- }, this);
- } else {
- }
- }
- },
- // Handle an event
- handleEvent: function (e) {
- var position = helpers.getRelativePosition(e, this.chart.chart);
- if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) {
- // See if we are touching one of the dataset boxes
- for (var i = 0; i < this.legendHitBoxes.length; ++i) {
- var hitBox = this.legendHitBoxes[i];
- if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) {
- // Touching an element
- if (this.options.onClick) {
- this.options.onClick.call(this, e, this.legendItems[i]);
- }
- break;
- }
- }
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.scale = {
- display: true,
- // grid line settings
- gridLines: {
- display: true,
- color: "rgba(0, 0, 0, 0.1)",
- lineWidth: 1,
- drawOnChartArea: true,
- drawTicks: true,
- zeroLineWidth: 1,
- zeroLineColor: "rgba(0,0,0,0.25)",
- offsetGridLines: false,
- },
- // scale label
- scaleLabel: {
- fontColor: '#333',
- fontFamily: 'Roboto, sans-serif ',
- fontSize: 14,
- fontStyle: 'normal',
- // actual label
- labelString: '',
- // display property
- display: false,
- },
- // label settings
- ticks: {
- beginAtZero: false,
- fontSize: 14,
- fontStyle: "normal",
- fontColor: "#333",
- fontFamily: "Roboto', sans-serif",
- maxRotation: 90,
- mirror: false,
- padding: 10,
- reverse: false,
- display: true,
- autoSkip: true,
- autoSkipPadding: 20,
- callback: function (value) {
- return '' + value;
- },
- },
- };
- Chart.Scale = Chart.Element.extend({
- // These methods are ordered by lifecyle. Utilities then follow.
- // Any function defined here is inherited by all scale types.
- // Any function can be extended by the scale type
- beforeUpdate: function () {
- helpers.callCallback(this.options.beforeUpdate, [this]);
- },
- update: function (maxWidth, maxHeight, margins) {
- // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
- this.beforeUpdate();
- // Absorb the master measurements
- this.maxWidth = maxWidth;
- this.maxHeight = maxHeight;
- this.margins = helpers.extend({
- left: 0,
- right: 0,
- top: 0,
- bottom: 0
- }, margins);
- // Dimensions
- this.beforeSetDimensions();
- this.setDimensions();
- this.afterSetDimensions();
- // Data min/max
- this.beforeDataLimits();
- this.determineDataLimits();
- this.afterDataLimits();
- // Ticks
- this.beforeBuildTicks();
- this.buildTicks();
- this.afterBuildTicks();
- this.beforeTickToLabelConversion();
- this.convertTicksToLabels();
- this.afterTickToLabelConversion();
- // Tick Rotation
- this.beforeCalculateTickRotation();
- this.calculateTickRotation();
- this.afterCalculateTickRotation();
- // Fit
- this.beforeFit();
- this.fit();
- this.afterFit();
- //
- this.afterUpdate();
- return this.minSize;
- },
- afterUpdate: function () {
- helpers.callCallback(this.options.afterUpdate, [this]);
- },
- //
- beforeSetDimensions: function () {
- helpers.callCallback(this.options.beforeSetDimensions, [this]);
- },
- setDimensions: function () {
- // Set the unconstrained dimension before label rotation
- if (this.isHorizontal()) {
- // Reset position before calculating rotation
- this.width = this.maxWidth;
- this.left = 0;
- this.right = this.width;
- } else {
- this.height = this.maxHeight;
- // Reset position before calculating rotation
- this.top = 0;
- this.bottom = this.height;
- }
- // Reset padding
- this.paddingLeft = 0;
- this.paddingTop = 0;
- this.paddingRight = 0;
- this.paddingBottom = 0;
- },
- afterSetDimensions: function () {
- helpers.callCallback(this.options.afterSetDimensions, [this]);
- },
- // Data limits
- beforeDataLimits: function () {
- helpers.callCallback(this.options.beforeDataLimits, [this]);
- },
- determineDataLimits: helpers.noop,
- afterDataLimits: function () {
- helpers.callCallback(this.options.afterDataLimits, [this]);
- },
- //
- beforeBuildTicks: function () {
- helpers.callCallback(this.options.beforeBuildTicks, [this]);
- },
- buildTicks: helpers.noop,
- afterBuildTicks: function () {
- helpers.callCallback(this.options.afterBuildTicks, [this]);
- },
- beforeTickToLabelConversion: function () {
- helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
- },
- convertTicksToLabels: function () {
- // Convert ticks to strings
- this.ticks = this.ticks.map(function (numericalTick, index, ticks) {
- if (this.options.ticks.userCallback) {
- return this.options.ticks.userCallback(numericalTick, index, ticks);
- }
- return this.options.ticks.callback(numericalTick, index, ticks);
- },
- this);
- },
- afterTickToLabelConversion: function () {
- helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
- },
- //
- beforeCalculateTickRotation: function () {
- helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
- },
- calculateTickRotation: function () {
- //Get the width of each grid by calculating the difference
- //between x offsets between 0 and 1.
- var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
- this.ctx.font = labelFont;
- var firstWidth = this.ctx.measureText(this.ticks[0]).width;
- var lastWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
- var firstRotated;
- this.labelRotation = 0;
- this.paddingRight = 0;
- this.paddingLeft = 0;
- if (this.options.display) {
- if (this.isHorizontal()) {
- this.paddingRight = lastWidth / 2 + 3;
- this.paddingLeft = firstWidth / 2 + 3;
- var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
- var labelWidth = originalLabelWidth;
- var cosRotation;
- var sinRotation;
- // Allow 3 pixels x2 padding either side for label readability
- // only the index matters for a dataset scale, but we want a consistent interface between scales
- var tickWidth = this.getPixelForTick(1) - this.getPixelForTick(0) - 6;
- //Max label rotation can be set or default to 90 - also act as a loop counter
- while (labelWidth > tickWidth && this.labelRotation < this.options.ticks.maxRotation) {
- cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
- sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
- firstRotated = cosRotation * firstWidth;
- // We're right aligning the text now.
- if (firstRotated + this.options.ticks.fontSize / 2 > this.yLabelWidth) {
- this.paddingLeft = firstRotated + this.options.ticks.fontSize / 2;
- }
- this.paddingRight = this.options.ticks.fontSize / 2;
- if (sinRotation * originalLabelWidth > this.maxHeight) {
- // go back one step
- this.labelRotation--;
- break;
- }
- this.labelRotation++;
- labelWidth = cosRotation * originalLabelWidth;
- }
- }
- }
- if (this.margins) {
- this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
- this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
- }
- },
- afterCalculateTickRotation: function () {
- helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
- },
- //
- beforeFit: function () {
- helpers.callCallback(this.options.beforeFit, [this]);
- },
- fit: function () {
- this.minSize = {
- width: 0,
- height: 0,
- };
- // Width
- if (this.isHorizontal()) {
- // subtract the margins to line up with the chartArea if we are a full width scale
- this.minSize.width = this.isFullWidth() ? this.maxWidth - this.margins.left - this.margins.right : this.maxWidth;
- } else {
- this.minSize.width = this.options.gridLines.display && this.options.display ? 10 : 0;
- }
- // height
- if (this.isHorizontal()) {
- this.minSize.height = this.options.gridLines.display && this.options.display ? 10 : 0;
- } else {
- this.minSize.height = this.maxHeight; // fill all the height
- }
- // Are we showing a title for the scale?
- if (this.options.scaleLabel.display) {
- if (this.isHorizontal()) {
- this.minSize.height += (this.options.scaleLabel.fontSize * 1.5);
- } else {
- this.minSize.width += (this.options.scaleLabel.fontSize * 1.5);
- }
- }
- if (this.options.ticks.display && this.options.display) {
- // Don't bother fitting the ticks if we are not showing them
- var labelFont = helpers.fontString(this.options.ticks.fontSize,
- this.options.ticks.fontStyle, this.options.ticks.fontFamily);
- if (this.isHorizontal()) {
- // A horizontal axis is more constrained by the height.
- this.longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
- // TODO - improve this calculation
- var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + 1.5 * this.options.ticks.fontSize;
- this.minSize.height = Math.min(this.maxHeight, this.minSize.height + labelHeight);
- labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
- this.ctx.font = labelFont;
- var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width;
- var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
- // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
- // by the font height
- var cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
- var sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
- this.paddingLeft = this.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
- this.paddingRight = this.labelRotation !== 0 ? (sinRotation * (this.options.ticks.fontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
- } else {
- // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
- var maxLabelWidth = this.maxWidth - this.minSize.width;
- var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
- // Account for padding
- if (!this.options.ticks.mirror) {
- largestTextWidth += this.options.ticks.padding;
- }
- if (largestTextWidth < maxLabelWidth) {
- // We don't need all the room
- this.minSize.width += largestTextWidth;
- } else {
- // Expand to max size
- this.minSize.width = this.maxWidth;
- }
- this.paddingTop = this.options.ticks.fontSize / 2;
- this.paddingBottom = this.options.ticks.fontSize / 2;
- }
- }
- if (this.margins) {
- this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
- this.paddingTop = Math.max(this.paddingTop - this.margins.top, 0);
- this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
- this.paddingBottom = Math.max(this.paddingBottom - this.margins.bottom, 0);
- }
- this.width = this.minSize.width;
- this.height = this.minSize.height;
- },
- afterFit: function () {
- helpers.callCallback(this.options.afterFit, [this]);
- },
- // Shared Methods
- isHorizontal: function () {
- return this.options.position === "top" || this.options.position === "bottom";
- },
- isFullWidth: function () {
- return (this.options.fullWidth);
- },
- // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
- getRightValue: function getRightValue(rawValue) {
- // Null and undefined values first
- if (rawValue === null || typeof (rawValue) === 'undefined') {
- return NaN;
- }
- // isNaN(object) returns true, so make sure NaN is checking for a number
- if (typeof (rawValue) === 'number' && isNaN(rawValue)) {
- return NaN;
- }
- // If it is in fact an object, dive in one more level
- if (typeof (rawValue) === "object") {
- if (rawValue instanceof Date) {
- return rawValue;
- } else {
- return getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
- }
- }
- // Value is good, return it
- return rawValue;
- },
- // Used to get the value to display in the tooltip for the data at the given index
- // function getLabelForIndex(index, datasetIndex)
- getLabelForIndex: helpers.noop,
- // Used to get data value locations. Value can either be an index or a numerical value
- getPixelForValue: helpers.noop,
- // Used for tick location, should
- getPixelForTick: function (index, includeOffset) {
- if (this.isHorizontal()) {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- var tickWidth = innerWidth / Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
- var pixel = (tickWidth * index) + this.paddingLeft;
- if (includeOffset) {
- pixel += tickWidth / 2;
- }
- var finalVal = this.left + Math.round(pixel);
- finalVal += this.isFullWidth() ? this.margins.left : 0;
- return finalVal;
- } else {
- var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
- return this.top + (index * (innerHeight / (this.ticks.length - 1)));
- }
- },
- // Utility for getting the pixel location of a percentage of scale
- getPixelForDecimal: function (decimal/*, includeOffset*/) {
- if (this.isHorizontal()) {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- var valueOffset = (innerWidth * decimal) + this.paddingLeft;
- var finalVal = this.left + Math.round(valueOffset);
- finalVal += this.isFullWidth() ? this.margins.left : 0;
- return finalVal;
- } else {
- return this.top + (decimal * this.height);
- }
- },
- // Actualy draw the scale on the canvas
- // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
- draw: function (chartArea) {
- if (this.options.display) {
- var setContextLineSettings;
- var isRotated = this.labelRotation !== 0;
- var skipRatio;
- var scaleLabelX;
- var scaleLabelY;
- var useAutoskipper = this.options.ticks.autoSkip;
- // figure out the maximum number of gridlines to show
- var maxTicks;
- if (this.options.ticks.maxTicksLimit) {
- maxTicks = this.options.ticks.maxTicksLimit;
- }
- // Make sure we draw text in the correct color and font
- this.ctx.fillStyle = this.options.ticks.fontColor;
- var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
- var cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
- var sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
- var longestRotatedLabel = this.longestLabelWidth * cosRotation;
- var rotatedLabelHeight = this.options.ticks.fontSize * sinRotation;
- if (this.isHorizontal()) {
- setContextLineSettings = true;
- var yTickStart = this.options.position === "bottom" ? this.top : this.bottom - 10;
- var yTickEnd = this.options.position === "bottom" ? this.top + 10 : this.bottom;
- skipRatio = false;
- if (((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
- skipRatio = 1 + Math.floor((((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
- }
- if (!useAutoskipper) {
- skipRatio = false;
- }
- // if they defined a max number of ticks,
- // increase skipRatio until that number is met
- if (maxTicks && this.ticks.length > maxTicks) {
- while (!skipRatio || this.ticks.length / (skipRatio || 1) > maxTicks) {
- if (!skipRatio) {
- skipRatio = 1;
- }
- skipRatio += 1;
- }
- }
- helpers.each(this.ticks, function (label, index) {
- // Blank ticks
- var isLastTick = this.ticks.length == index + 1;
- var shouldSkip = skipRatio > 1 && index % skipRatio > 0;
- if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
- return;
- }
- var xLineValue = this.getPixelForTick(index); // xvalues for grid lines
- var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
- if (this.options.gridLines.display) {
- if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
- // Draw the first index specially
- this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
- this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
- setContextLineSettings = true; // reset next time
- } else if (setContextLineSettings) {
- this.ctx.lineWidth = this.options.gridLines.lineWidth;
- this.ctx.strokeStyle = this.options.gridLines.color;
- setContextLineSettings = false;
- }
- xLineValue += helpers.aliasPixel(this.ctx.lineWidth);
- // Draw the label area
- this.ctx.beginPath();
- if (this.options.gridLines.drawTicks) {
- this.ctx.moveTo(xLineValue, yTickStart);
- this.ctx.lineTo(xLineValue, yTickEnd);
- }
- // Draw the chart area
- if (this.options.gridLines.drawOnChartArea) {
- this.ctx.moveTo(xLineValue, chartArea.top);
- this.ctx.lineTo(xLineValue, chartArea.bottom);
- }
- // Need to stroke in the loop because we are potentially changing line widths & colours
- this.ctx.stroke();
- }
- if (this.options.ticks.display) {
- this.ctx.save();
- this.ctx.translate(xLabelValue, (isRotated) ? this.top + 12 : this.options.position === "top" ? this.bottom - 10 : this.top + 10);
- this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
- this.ctx.font = labelFont;
- this.ctx.textAlign = (isRotated) ? "right" : "center";
- this.ctx.textBaseline = (isRotated) ? "middle" : this.options.position === "top" ? "bottom" : "top";
- this.ctx.fillText(label, 0, 0);
- this.ctx.restore();
- }
- }, this);
- if (this.options.scaleLabel.display) {
- // Draw the scale label
- this.ctx.textAlign = "center";
- this.ctx.textBaseline = 'middle';
- this.ctx.fillStyle = this.options.scaleLabel.fontColor; // render in correct colour
- this.ctx.font = helpers.fontString(this.options.scaleLabel.fontSize, this.options.scaleLabel.fontStyle, this.options.scaleLabel.fontFamily);
- scaleLabelX = this.left + ((this.right - this.left) / 2); // midpoint of the width
- scaleLabelY = this.options.position === 'bottom' ? this.bottom - (this.options.scaleLabel.fontSize / 2) : this.top + (this.options.scaleLabel.fontSize / 2);
- this.ctx.fillText(this.options.scaleLabel.labelString, scaleLabelX, scaleLabelY);
- }
- } else {
- setContextLineSettings = true;
- var xTickStart = this.options.position === "right" ? this.left : this.right - 5;
- var xTickEnd = this.options.position === "right" ? this.left + 5 : this.right;
- helpers.each(this.ticks, function (label, index) {
- // If the callback returned a null or undefined value, do not draw this line
- if (label === undefined || label === null) {
- return;
- }
- var yLineValue = this.getPixelForTick(index); // xvalues for grid lines
- if (this.options.gridLines.display) {
- if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
- // Draw the first index specially
- this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
- this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
- setContextLineSettings = true; // reset next time
- } else if (setContextLineSettings) {
- this.ctx.lineWidth = this.options.gridLines.lineWidth;
- this.ctx.strokeStyle = this.options.gridLines.color;
- setContextLineSettings = false;
- }
- yLineValue += helpers.aliasPixel(this.ctx.lineWidth);
- // Draw the label area
- this.ctx.beginPath();
- if (this.options.gridLines.drawTicks) {
- this.ctx.moveTo(xTickStart, yLineValue);
- this.ctx.lineTo(xTickEnd, yLineValue);
- }
- // Draw the chart area
- if (this.options.gridLines.drawOnChartArea) {
- this.ctx.moveTo(chartArea.left, yLineValue);
- this.ctx.lineTo(chartArea.right, yLineValue);
- }
- // Need to stroke in the loop because we are potentially changing line widths & colours
- this.ctx.stroke();
- }
- if (this.options.ticks.display) {
- var xLabelValue;
- var yLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
- this.ctx.save();
- if (this.options.position === "left") {
- if (this.options.ticks.mirror) {
- xLabelValue = this.right + this.options.ticks.padding;
- this.ctx.textAlign = "left";
- } else {
- xLabelValue = this.right - this.options.ticks.padding;
- this.ctx.textAlign = "right";
- }
- } else {
- // right side
- if (this.options.ticks.mirror) {
- xLabelValue = this.left - this.options.ticks.padding;
- this.ctx.textAlign = "right";
- } else {
- xLabelValue = this.left + this.options.ticks.padding;
- this.ctx.textAlign = "left";
- }
- }
- this.ctx.translate(xLabelValue, yLabelValue);
- this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
- this.ctx.font = labelFont;
- this.ctx.textBaseline = "middle";
- this.ctx.fillText(label, 0, 0);
- this.ctx.restore();
- }
- }, this);
- if (this.options.scaleLabel.display) {
- // Draw the scale label
- scaleLabelX = this.options.position === 'left' ? this.left + (this.options.scaleLabel.fontSize / 2) : this.right - (this.options.scaleLabel.fontSize / 2);
- scaleLabelY = this.top + ((this.bottom - this.top) / 2);
- var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
- this.ctx.save();
- this.ctx.translate(scaleLabelX, scaleLabelY);
- this.ctx.rotate(rotation);
- this.ctx.textAlign = "center";
- this.ctx.fillStyle = this.options.scaleLabel.fontColor; // render in correct colour
- this.ctx.font = helpers.fontString(this.options.scaleLabel.fontSize, this.options.scaleLabel.fontStyle, this.options.scaleLabel.fontFamily);
- this.ctx.textBaseline = 'middle';
- this.ctx.fillText(this.options.scaleLabel.labelString, 0, 0);
- this.ctx.restore();
- }
- }
- // Draw the line at the edge of the axis
- this.ctx.lineWidth = this.options.gridLines.lineWidth;
- this.ctx.strokeStyle = this.options.gridLines.color;
- var x1 = this.left, x2 = this.right, y1 = this.top, y2 = this.bottom;
- if (this.isHorizontal()) {
- y1 = y2 = this.options.position === 'top' ? this.bottom : this.top;
- y1 += helpers.aliasPixel(this.ctx.lineWidth);
- y2 += helpers.aliasPixel(this.ctx.lineWidth);
- } else {
- x1 = x2 = this.options.position === 'left' ? this.right : this.left;
- x1 += helpers.aliasPixel(this.ctx.lineWidth);
- x2 += helpers.aliasPixel(this.ctx.lineWidth);
- }
- this.ctx.moveTo(x1, y1);
- this.ctx.lineTo(x2, y2);
- this.ctx.stroke();
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.scaleService = {
- // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
- // use the new chart options to grab the correct scale
- constructors: {},
- // Use a registration function so that we can move to an ES6 map when we no longer need to support
- // old browsers
- // Scale config defaults
- defaults: {},
- registerScaleType: function (type, scaleConstructor, defaults) {
- this.constructors[type] = scaleConstructor;
- this.defaults[type] = helpers.clone(defaults);
- },
- getScaleConstructor: function (type) {
- return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
- },
- getScaleDefaults: function (type) {
- // Return the scale defaults merged with the global settings so that we always use the latest ones
- return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
- },
- addScalesToLayout: function (chartInstance) {
- // Adds each scale to the chart.boxes array to be sized accordingly
- helpers.each(chartInstance.scales, function (scale) {
- Chart.layoutService.addBox(chartInstance, scale);
- });
- },
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.title = {
- display: false,
- position: 'top',
- fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
- fontColor: '#333',
- fontFamily: 'Roboto',
- fontSize: 14,
- fontStyle: 'bold',
- padding: 10,
- // actual title
- text: '',
- };
- Chart.Title = Chart.Element.extend({
- initialize: function (config) {
- helpers.extend(this, config);
- this.options = helpers.configMerge(Chart.defaults.global.title, config.options);
- // Contains hit boxes for each dataset (in dataset order)
- this.legendHitBoxes = [];
- },
- // These methods are ordered by lifecyle. Utilities then follow.
- beforeUpdate: helpers.noop,
- update: function (maxWidth, maxHeight, margins) {
- // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
- this.beforeUpdate();
- // Absorb the master measurements
- this.maxWidth = maxWidth;
- this.maxHeight = maxHeight;
- this.margins = margins;
- // Dimensions
- this.beforeSetDimensions();
- this.setDimensions();
- this.afterSetDimensions();
- // Labels
- this.beforeBuildLabels();
- this.buildLabels();
- this.afterBuildLabels();
- // Fit
- this.beforeFit();
- this.fit();
- this.afterFit();
- //
- this.afterUpdate();
- return this.minSize;
- },
- afterUpdate: helpers.noop,
- //
- beforeSetDimensions: helpers.noop,
- setDimensions: function () {
- // Set the unconstrained dimension before label rotation
- if (this.isHorizontal()) {
- // Reset position before calculating rotation
- this.width = this.maxWidth;
- this.left = 0;
- this.right = this.width;
- } else {
- this.height = this.maxHeight;
- // Reset position before calculating rotation
- this.top = 0;
- this.bottom = this.height;
- }
- // Reset padding
- this.paddingLeft = 0;
- this.paddingTop = 0;
- this.paddingRight = 0;
- this.paddingBottom = 0;
- // Reset minSize
- this.minSize = {
- width: 0,
- height: 0,
- };
- },
- afterSetDimensions: helpers.noop,
- //
- beforeBuildLabels: helpers.noop,
- buildLabels: helpers.noop,
- afterBuildLabels: helpers.noop,
- //
- beforeFit: helpers.noop,
- fit: function () {
- var ctx = this.ctx;
- var titleFont = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
- // Width
- if (this.isHorizontal()) {
- this.minSize.width = this.maxWidth; // fill all the width
- } else {
- this.minSize.width = 0;
- }
- // height
- if (this.isHorizontal()) {
- this.minSize.height = 0;
- } else {
- this.minSize.height = this.maxHeight; // fill all the height
- }
- // Increase sizes here
- if (this.isHorizontal()) {
- // Title
- if (this.options.display) {
- this.minSize.height += this.options.fontSize + (this.options.padding * 2);
- }
- } else {
- // TODO vertical
- }
- this.width = this.minSize.width;
- this.height = this.minSize.height;
- },
- afterFit: helpers.noop,
- // Shared Methods
- isHorizontal: function () {
- return this.options.position == "top" || this.options.position == "bottom";
- },
- // Actualy draw the title block on the canvas
- draw: function () {
- if (this.options.display) {
- var ctx = this.ctx;
- var titleX, titleY;
- // Horizontal
- if (this.isHorizontal()) {
- // Title
- if (this.options.display) {
- ctx.textAlign = "center";
- ctx.textBaseline = 'middle';
- ctx.fillStyle = this.options.fontColor; // render in correct colour
- ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
- titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
- titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height
- ctx.fillText(this.options.text, titleX, titleY);
- }
- } else {
- // Title
- if (this.options.display) {
- titleX = this.options.position == 'left' ? this.left + (this.options.fontSize / 2) : this.right - (this.options.fontSize / 2);
- titleY = this.top + ((this.bottom - this.top) / 2);
- var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
- ctx.save();
- ctx.translate(titleX, titleY);
- ctx.rotate(rotation);
- ctx.textAlign = "center";
- ctx.fillStyle = this.options.fontColor; // render in correct colour
- ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
- ctx.textBaseline = 'middle';
- ctx.fillText(this.options.text, 0, 0);
- ctx.restore();
- }
- }
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.tooltips = {
- enabled: true,
- custom: null,
- mode: 'single',
- backgroundColor: "rgba(0,0,0,0.8)",
- titleFontFamily: "'Roboto', sans-serif",
- titleFontSize: 12,
- titleFontStyle: "bold",
- titleSpacing: 2,
- titleMarginBottom: 6,
- titleColor: "#fff",
- titleAlign: "left",
- bodyFontFamily: "'Roboto', sans-serif",
- bodyFontSize: 12,
- bodyFontStyle: "normal",
- bodySpacing: 2,
- bodyColor: "#fff",
- bodyAlign: "left",
- footerFontFamily: "'Roboto', sans-serif",
- footerFontSize: 12,
- footerFontStyle: "normal",
- footerSpacing: 2,
- footerMarginTop: 6,
- footerColor: "#fff",
- footerAlign: "left",
- yPadding: 6,
- xPadding: 6,
- caretSize: 5,
- cornerRadius: 6,
- multiKeyBackground: '#fff',
- callbacks: {
- // Args are: (tooltipItems, data)
- beforeTitle: helpers.noop,
- title: function (tooltipItems, data) {
- // Pick first xLabel for now
- var title = '';
- if (tooltipItems.length > 0) {
- if (tooltipItems[0].xLabel) {
- title = tooltipItems[0].xLabel;
- } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
- title = data.labels[tooltipItems[0].index];
- }
- }
- return title;
- },
- afterTitle: helpers.noop,
- // Args are: (tooltipItems, data)
- beforeBody: helpers.noop,
- // Args are: (tooltipItem, data)
- beforeLabel: helpers.noop,
- label: function (tooltipItem, data) {
- var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
- return datasetLabel + ': ' + tooltipItem.yLabel;
- },
- afterLabel: helpers.noop,
- // Args are: (tooltipItems, data)
- afterBody: helpers.noop,
- // Args are: (tooltipItems, data)
- beforeFooter: helpers.noop,
- footer: helpers.noop,
- afterFooter: helpers.noop,
- },
- };
- // Helper to push or concat based on if the 2nd parameter is an array or not
- function pushOrConcat(base, toPush) {
- if (toPush) {
- if (helpers.isArray(toPush)) {
- base = base.concat(toPush);
- } else {
- base.push(toPush);
- }
- }
- return base;
- }
- Chart.Tooltip = Chart.Element.extend({
- initialize: function () {
- var options = this._options;
- helpers.extend(this, {
- _model: {
- // Positioning
- xPadding: options.tooltips.xPadding,
- yPadding: options.tooltips.yPadding,
- // Body
- bodyColor: options.tooltips.bodyColor,
- _bodyFontFamily: options.tooltips.bodyFontFamily,
- _bodyFontStyle: options.tooltips.bodyFontStyle,
- _bodyAlign: options.tooltips.bodyAlign,
- bodyFontSize: options.tooltips.bodyFontSize,
- bodySpacing: options.tooltips.bodySpacing,
- // Title
- titleColor: options.tooltips.titleColor,
- _titleFontFamily: options.tooltips.titleFontFamily,
- _titleFontStyle: options.tooltips.titleFontStyle,
- titleFontSize: options.tooltips.titleFontSize,
- _titleAlign: options.tooltips.titleAlign,
- titleSpacing: options.tooltips.titleSpacing,
- titleMarginBottom: options.tooltips.titleMarginBottom,
- // Footer
- footerColor: options.tooltips.footerColor,
- _footerFontFamily: options.tooltips.footerFontFamily,
- _footerFontStyle: options.tooltips.footerFontStyle,
- footerFontSize: options.tooltips.footerFontSize,
- _footerAlign: options.tooltips.footerAlign,
- footerSpacing: options.tooltips.footerSpacing,
- footerMarginTop: options.tooltips.footerMarginTop,
- // Appearance
- caretSize: options.tooltips.caretSize,
- cornerRadius: options.tooltips.cornerRadius,
- backgroundColor: options.tooltips.backgroundColor,
- opacity: 0,
- legendColorBackground: options.tooltips.multiKeyBackground,
- },
- });
- },
- // Get the title
- // Args are: (tooltipItem, data)
- getTitle: function () {
- var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments),
- title = this._options.tooltips.callbacks.title.apply(this, arguments),
- afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, arguments);
- var lines = [];
- lines = pushOrConcat(lines, beforeTitle);
- lines = pushOrConcat(lines, title);
- lines = pushOrConcat(lines, afterTitle);
- return lines;
- },
- // Args are: (tooltipItem, data)
- getBeforeBody: function () {
- var lines = this._options.tooltips.callbacks.beforeBody.apply(this, arguments);
- return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
- },
- // Args are: (tooltipItem, data)
- getBody: function (tooltipItems, data) {
- var lines = [];
- helpers.each(tooltipItems, function (bodyItem) {
- var beforeLabel = this._options.tooltips.callbacks.beforeLabel.call(this, bodyItem, data) || '';
- var bodyLabel = this._options.tooltips.callbacks.label.call(this, bodyItem, data) || '';
- var afterLabel = this._options.tooltips.callbacks.afterLabel.call(this, bodyItem, data) || '';
- lines.push(beforeLabel + bodyLabel + afterLabel);
- }, this);
- return lines;
- },
- // Args are: (tooltipItem, data)
- getAfterBody: function () {
- var lines = this._options.tooltips.callbacks.afterBody.apply(this, arguments);
- return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
- },
- // Get the footer and beforeFooter and afterFooter lines
- // Args are: (tooltipItem, data)
- getFooter: function () {
- var beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments);
- var footer = this._options.tooltips.callbacks.footer.apply(this, arguments);
- var afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments);
- var lines = [];
- lines = pushOrConcat(lines, beforeFooter);
- lines = pushOrConcat(lines, footer);
- lines = pushOrConcat(lines, afterFooter);
- return lines;
- },
- getAveragePosition: function (elements) {
- if (!elements.length) {
- return false;
- }
- var xPositions = [];
- var yPositions = [];
- helpers.each(elements, function (el) {
- if (el) {
- var pos = el.tooltipPosition();
- xPositions.push(pos.x);
- yPositions.push(pos.y);
- }
- });
- var x = 0, y = 0;
- for (var i = 0; i < xPositions.length; i++) {
- x += xPositions[i];
- y += yPositions[i];
- }
- return {
- x: Math.round(x / xPositions.length),
- y: Math.round(y / xPositions.length)
- };
- },
- update: function (changed) {
- if (this._active.length) {
- this._model.opacity = 1;
- var element = this._active[0],
- labelColors = [],
- tooltipPosition;
- var tooltipItems = [];
- if (this._options.tooltips.mode === 'single') {
- var yScale = element._yScale || element._scale; // handle radar || polarArea charts
- tooltipItems.push({
- xLabel: element._xScale ? element._xScale.getLabelForIndex(element._index, element._datasetIndex) : '',
- yLabel: yScale ? yScale.getLabelForIndex(element._index, element._datasetIndex) : '',
- index: element._index,
- datasetIndex: element._datasetIndex,
- });
- tooltipPosition = this.getAveragePosition(this._active);
- } else {
- helpers.each(this._data.datasets, function (dataset, datasetIndex) {
- if (!helpers.isDatasetVisible(dataset)) {
- return;
- }
- var currentElement = dataset.metaData[element._index];
- if (currentElement) {
- var yScale = element._yScale || element._scale; // handle radar || polarArea charts
- tooltipItems.push({
- xLabel: currentElement._xScale ? currentElement._xScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
- yLabel: yScale ? yScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
- index: element._index,
- datasetIndex: datasetIndex,
- });
- }
- });
- helpers.each(this._active, function (active) {
- if (active) {
- labelColors.push({
- borderColor: active._view.borderColor,
- backgroundColor: active._view.backgroundColor
- });
- }
- }, this);
- tooltipPosition = this.getAveragePosition(this._active);
- tooltipPosition.y = this._active[0]._yScale.getPixelForDecimal(0.5);
- }
- // Build the Text Lines
- helpers.extend(this._model, {
- title: this.getTitle(tooltipItems, this._data),
- beforeBody: this.getBeforeBody(tooltipItems, this._data),
- body: this.getBody(tooltipItems, this._data),
- afterBody: this.getAfterBody(tooltipItems, this._data),
- footer: this.getFooter(tooltipItems, this._data),
- });
- helpers.extend(this._model, {
- x: Math.round(tooltipPosition.x),
- y: Math.round(tooltipPosition.y),
- caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
- labelColors: labelColors,
- });
- // We need to determine alignment of
- var tooltipSize = this.getTooltipSize(this._model);
- this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
- helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize));
- } else {
- this._model.opacity = 0;
- }
- if (changed && this._options.tooltips.custom) {
- this._options.tooltips.custom.call(this, this._model);
- }
- return this;
- },
- getTooltipSize: function getTooltipSize(vm) {
- var ctx = this._chart.ctx;
- var size = {
- height: vm.yPadding * 2, // Tooltip Padding
- width: 0
- };
- var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length;
- size.height += vm.title.length * vm.titleFontSize; // Title Lines
- size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
- size.height += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin
- size.height += combinedBodyLength * vm.bodyFontSize; // Body Lines
- size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
- size.height += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin
- size.height += vm.footer.length * (vm.footerFontSize); // Footer Lines
- size.height += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing
- // Width
- ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
- helpers.each(vm.title, function (line) {
- size.width = Math.max(size.width, ctx.measureText(line).width);
- });
- ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
- helpers.each(vm.beforeBody.concat(vm.afterBody), function (line) {
- size.width = Math.max(size.width, ctx.measureText(line).width);
- }, this);
- helpers.each(vm.body, function (line) {
- size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
- }, this);
- ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
- helpers.each(vm.footer, function (line) {
- size.width = Math.max(size.width, ctx.measureText(line).width);
- });
- size.width += 2 * vm.xPadding;
- return size;
- },
- determineAlignment: function determineAlignment(size) {
- this._model.xAlign = this._model.yAlign = "center";
- if (this._model.y < size.height) {
- this._model.yAlign = 'top';
- } else if (this._model.y > (this._chart.height - size.height)) {
- this._model.yAlign = 'bottom';
- }
- var lf, rf; // functions to determine left, right alignment
- var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
- var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
- var _this = this;
- var midX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2;
- var midY = (this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2;
- if (this._model.yAlign === 'center') {
- lf = function (x) {
- return x <= midX;
- };
- rf = function (x) {
- return x > midX;
- };
- olf = function (x) {
- return x + size.width > _this._chart.width;
- };
- orf = function (x) {
- return x - size.width < 0;
- };
- yf = function (y) {
- return y <= midY ? 'top' : 'bottom';
- };
- } else {
- lf = function (x) {
- return x <= (size.width / 2);
- };
- rf = function (x) {
- return x >= (_this._chart.width - (size.width / 2));
- };
- }
- if (lf(this._model.x)) {
- this._model.xAlign = 'left';
- // Is tooltip too wide and goes over the right side of the chart.?
- if (olf(this._model.x)) {
- this._model.xAlign = 'center';
- this._model.yAlign = yf(this._model.y);
- }
- } else if (rf(this._model.x)) {
- this._model.xAlign = 'right';
- // Is tooltip too wide and goes outside left edge of canvas?
- if (orf(this._model.x)) {
- this._model.xAlign = 'center';
- this._model.yAlign = yf(this._model.y);
- }
- }
- },
- getBackgroundPoint: function getBackgroundPoint(vm, size) {
- // Background Position
- var pt = {
- x: vm.x,
- y: vm.y
- };
- if (vm.xAlign === 'right') {
- pt.x -= size.width;
- } else if (vm.xAlign === 'center') {
- pt.x -= (size.width / 2);
- }
- if (vm.yAlign === 'top') {
- pt.y += vm.caretPadding + vm.caretSize;
- } else if (vm.yAlign === 'bottom') {
- pt.y -= size.height + vm.caretPadding + vm.caretSize;
- } else {
- pt.y -= (size.height / 2);
- }
- if (vm.yAlign == 'center') {
- if (vm.xAlign === 'left') {
- pt.x += vm.caretPadding + vm.caretSize;
- } else if (vm.xAlign === 'right') {
- pt.x -= vm.caretPadding + vm.caretSize;
- }
- } else {
- if (vm.xAlign === 'left') {
- pt.x -= vm.cornerRadius + vm.caretPadding;
- } else if (vm.xAlign === 'right') {
- pt.x += vm.cornerRadius + vm.caretPadding;
- }
- }
- return pt;
- },
- drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) {
- var vm = this._view;
- var ctx = this._chart.ctx;
- var x1, x2, x3;
- var y1, y2, y3;
- if (vm.yAlign === 'center') {
- // Left or right side
- if (vm.xAlign === 'left') {
- x1 = tooltipPoint.x;
- x2 = x1 - vm.caretSize;
- x3 = x1;
- } else {
- x1 = tooltipPoint.x + size.width;
- x2 = x1 + vm.caretSize;
- x3 = x1;
- }
- y2 = tooltipPoint.y + (size.height / 2);
- y1 = y2 - vm.caretSize;
- y3 = y2 + vm.caretSize;
- } else {
- if (vm.xAlign === 'left') {
- x1 = tooltipPoint.x + vm.cornerRadius;
- x2 = x1 + vm.caretSize;
- x3 = x2 + vm.caretSize;
- } else if (vm.xAlign === 'right') {
- x1 = tooltipPoint.x + size.width - vm.cornerRadius;
- x2 = x1 - vm.caretSize;
- x3 = x2 - vm.caretSize;
- } else {
- x2 = tooltipPoint.x + (size.width / 2);
- x1 = x2 - vm.caretSize;
- x3 = x2 + vm.caretSize;
- }
- if (vm.yAlign === 'top') {
- y1 = tooltipPoint.y;
- y2 = y1 - vm.caretSize;
- y3 = y1;
- } else {
- y1 = tooltipPoint.y + size.height;
- y2 = y1 + vm.caretSize;
- y3 = y1;
- }
- }
- ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(opacity).rgbString();
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
- ctx.lineTo(x3, y3);
- ctx.closePath();
- ctx.fill();
- },
- drawTitle: function drawTitle(pt, vm, ctx, opacity) {
- if (vm.title.length) {
- ctx.textAlign = vm._titleAlign;
- ctx.textBaseline = "top";
- ctx.fillStyle = helpers.color(vm.titleColor).alpha(opacity).rgbString();
- ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
- helpers.each(vm.title, function (title, i) {
- ctx.fillText(title, pt.x, pt.y);
- pt.y += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing
- if (i + 1 === vm.title.length) {
- pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
- }
- }, this);
- }
- },
- drawBody: function drawBody(pt, vm, ctx, opacity) {
- ctx.textAlign = vm._bodyAlign;
- ctx.textBaseline = "top";
- ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbString();
- ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
- // Before Body
- helpers.each(vm.beforeBody, function (beforeBody) {
- ctx.fillText(beforeBody, pt.x, pt.y);
- pt.y += vm.bodyFontSize + vm.bodySpacing;
- });
- helpers.each(vm.body, function (body, i) {
- // Draw Legend-like boxes if needed
- if (this._options.tooltips.mode !== 'single') {
- // Fill a white rect so that colours merge nicely if the opacity is < 1
- ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
- ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
- // Border
- ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
- ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
- // Inner square
- ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
- ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
- ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
- }
- // Body Line
- ctx.fillText(body, pt.x + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
- pt.y += vm.bodyFontSize + vm.bodySpacing;
- }, this);
- // After Body
- helpers.each(vm.afterBody, function (afterBody) {
- ctx.fillText(afterBody, pt.x, pt.y);
- pt.y += vm.bodyFontSize;
- });
- pt.y -= vm.bodySpacing; // Remove last body spacing
- },
- drawFooter: function drawFooter(pt, vm, ctx, opacity) {
- if (vm.footer.length) {
- pt.y += vm.footerMarginTop;
- ctx.textAlign = vm._footerAlign;
- ctx.textBaseline = "top";
- ctx.fillStyle = helpers.color(vm.footerColor).alpha(opacity).rgbString();
- ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
- helpers.each(vm.footer, function (footer) {
- ctx.fillText(footer, pt.x, pt.y);
- pt.y += vm.footerFontSize + vm.footerSpacing;
- }, this);
- }
- },
- draw: function draw() {
- var ctx = this._chart.ctx;
- var vm = this._view;
- if (vm.opacity === 0) {
- return;
- }
- var caretPadding = vm.caretPadding;
- var tooltipSize = this.getTooltipSize(vm);
- var pt = {
- x: vm.x,
- y: vm.y
- };
- // IE11/Edge does not like very small opacities, so snap to 0
- var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
- if (this._options.tooltips.enabled) {
- // Draw Background
- ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(opacity).rgbString();
- helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
- ctx.fill();
- // Draw Caret
- this.drawCaret(pt, tooltipSize, opacity, caretPadding);
- // Draw Title, Body, and Footer
- pt.x += vm.xPadding;
- pt.y += vm.yPadding;
- // Titles
- this.drawTitle(pt, vm, ctx, opacity);
- // Body
- this.drawBody(pt, vm, ctx, opacity);
- // Footer
- this.drawFooter(pt, vm, ctx, opacity);
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.bar = {
- hover: {
- mode: "label"
- },
- scales: {
- xAxes: [{
- type: "category",
- // Specific to Bar Controller
- categoryPercentage: 0.8,
- barPercentage: 0.9,
- // grid line settings
- gridLines: {
- offsetGridLines: true,
- },
- }],
- yAxes: [{
- type: "linear",
- }],
- },
- };
- Chart.controllers.bar = Chart.DatasetController.extend({
- initialize: function (chart, datasetIndex) {
- Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
- // Use this to indicate that this is a bar dataset.
- this.getDataset().bar = true;
- },
- // Get the number of datasets that display bars. We use this to correctly calculate the bar width
- getBarCount: function getBarCount() {
- var barCount = 0;
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (helpers.isDatasetVisible(dataset) && dataset.bar) {
- ++barCount;
- }
- }, this);
- return barCount;
- },
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- }, this);
- },
- addElementAndReset: function (index) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var rectangle = new Chart.elements.Rectangle({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- var numBars = this.getBarCount();
- this.updateElement(rectangle, index, true, numBars);
- this.getDataset().metaData.splice(index, 0, rectangle);
- },
- update: function update(reset) {
- var numBars = this.getBarCount();
- helpers.each(this.getDataset().metaData, function (rectangle, index) {
- this.updateElement(rectangle, index, reset, numBars);
- }, this);
- },
- updateElement: function updateElement(rectangle, index, reset, numBars) {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var yScalePoint;
- if (yScale.min < 0 && yScale.max < 0) {
- // all less than 0. use the top
- yScalePoint = yScale.getPixelForValue(yScale.max);
- } else if (yScale.min > 0 && yScale.max > 0) {
- yScalePoint = yScale.getPixelForValue(yScale.min);
- } else {
- yScalePoint = yScale.getPixelForValue(0);
- }
- helpers.extend(rectangle, {
- // Utility
- _chart: this.chart.chart,
- _xScale: xScale,
- _yScale: yScale,
- _datasetIndex: this.index,
- _index: index,
- // Desired view properties
- _model: {
- x: this.calculateBarX(index, this.index),
- y: reset ? yScalePoint : this.calculateBarY(index, this.index),
- // Tooltip
- label: this.chart.data.labels[index],
- datasetLabel: this.getDataset().label,
- // Appearance
- base: this.calculateBarBase(this.index, index),
- width: this.calculateBarWidth(numBars),
- backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor),
- borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor),
- borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth),
- },
- });
- rectangle.pivot();
- },
- calculateBarBase: function (datasetIndex, index) {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var base = 0;
- if (yScale.options.stacked) {
- var value = this.chart.data.datasets[datasetIndex].data[index];
- if (value < 0) {
- for (var i = 0; i < datasetIndex; i++) {
- var negDS = this.chart.data.datasets[i];
- if (helpers.isDatasetVisible(negDS) && negDS.yAxisID === yScale.id && negDS.bar) {
- base += negDS.data[index] < 0 ? negDS.data[index] : 0;
- }
- }
- } else {
- for (var j = 0; j < datasetIndex; j++) {
- var posDS = this.chart.data.datasets[j];
- if (helpers.isDatasetVisible(posDS) && posDS.yAxisID === yScale.id && posDS.bar) {
- base += posDS.data[index] > 0 ? posDS.data[index] : 0;
- }
- }
- }
- return yScale.getPixelForValue(base);
- }
- base = yScale.getPixelForValue(yScale.min);
- if (yScale.beginAtZero || ((yScale.min <= 0 && yScale.max >= 0) || (yScale.min >= 0 && yScale.max <= 0))) {
- base = yScale.getPixelForValue(0, 0);
- //base += yScale.options.gridLines.lineWidth;
- } else if (yScale.min < 0 && yScale.max < 0) {
- // All values are negative. Use the top as the base
- base = yScale.getPixelForValue(yScale.max);
- }
- return base;
- },
- getRuler: function () {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var datasetCount = this.getBarCount();
- var tickWidth = (function () {
- var min = xScale.getPixelForTick(1) - xScale.getPixelForTick(0);
- for (var i = 2; i < this.getDataset().data.length; i++) {
- min = Math.min(xScale.getPixelForTick(i) - xScale.getPixelForTick(i - 1), min);
- }
- return min;
- }).call(this);
- var categoryWidth = tickWidth * xScale.options.categoryPercentage;
- var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
- var fullBarWidth = categoryWidth / datasetCount;
- var barWidth = fullBarWidth * xScale.options.barPercentage;
- var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
- return {
- datasetCount: datasetCount,
- tickWidth: tickWidth,
- categoryWidth: categoryWidth,
- categorySpacing: categorySpacing,
- fullBarWidth: fullBarWidth,
- barWidth: barWidth,
- barSpacing: barSpacing,
- };
- },
- calculateBarWidth: function () {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var ruler = this.getRuler();
- return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
- },
- // Get bar index from the given dataset index accounting for the fact that not all bars are visible
- getBarIndex: function (datasetIndex) {
- var barIndex = 0;
- for (var j = 0; j < datasetIndex; ++j) {
- if (helpers.isDatasetVisible(this.chart.data.datasets[j]) && this.chart.data.datasets[j].bar) {
- ++barIndex;
- }
- }
- return barIndex;
- },
- calculateBarX: function (index, datasetIndex) {
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var barIndex = this.getBarIndex(datasetIndex);
- var ruler = this.getRuler();
- var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
- leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0;
- if (xScale.options.stacked) {
- return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
- }
- return leftTick +
- (ruler.barWidth / 2) +
- ruler.categorySpacing +
- (ruler.barWidth * barIndex) +
- (ruler.barSpacing / 2) +
- (ruler.barSpacing * barIndex);
- },
- calculateBarY: function (index, datasetIndex) {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var value = this.getDataset().data[index];
- if (yScale.options.stacked) {
- var sumPos = 0,
- sumNeg = 0;
- for (var i = 0; i < datasetIndex; i++) {
- var ds = this.chart.data.datasets[i];
- if (helpers.isDatasetVisible(ds) && ds.bar && ds.yAxisID === yScale.id) {
- if (ds.data[index] < 0) {
- sumNeg += ds.data[index] || 0;
- } else {
- sumPos += ds.data[index] || 0;
- }
- }
- }
- if (value < 0) {
- return yScale.getPixelForValue(sumNeg + value);
- } else {
- return yScale.getPixelForValue(sumPos + value);
- }
- return yScale.getPixelForValue(value);
- }
- return yScale.getPixelForValue(value);
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- helpers.each(this.getDataset().metaData, function (rectangle, index) {
- var d = this.getDataset().data[index];
- if (d !== null && d !== undefined && !isNaN(d)) {
- rectangle.transition(easingDecimal).draw();
- }
- }, this);
- },
- setHoverStyle: function (rectangle) {
- var dataset = this.chart.data.datasets[rectangle._datasetIndex];
- var index = rectangle._index;
- rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, rectangle._model.borderWidth);
- },
- removeHoverStyle: function (rectangle) {
- var dataset = this.chart.data.datasets[rectangle._datasetIndex];
- var index = rectangle._index;
- rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor);
- rectangle._model.borderColor = rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor);
- rectangle._model.borderWidth = rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth);
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.bubble = {
- hover: {
- mode: "single"
- },
- scales: {
- xAxes: [{
- type: "linear", // bubble should probably use a linear scale by default
- position: "bottom",
- id: "x-axis-0", // need an ID so datasets can reference the scale
- }],
- yAxes: [{
- type: "linear",
- position: "left",
- id: "y-axis-0",
- }],
- },
- tooltips: {
- template: "(<%= value.x %>, <%= value.y %>, <%= value.r %>)",
- multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>, <%= value.r %>)",
- },
- };
- Chart.controllers.bubble = Chart.DatasetController.extend({
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- }, this);
- },
- addElementAndReset: function (index) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var point = new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- // Reset the point
- this.updateElement(point, index, true);
- // Add to the points array
- this.getDataset().metaData.splice(index, 0, point);
- },
- update: function update(reset) {
- var points = this.getDataset().metaData;
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var scaleBase;
- if (yScale.min < 0 && yScale.max < 0) {
- scaleBase = yScale.getPixelForValue(yScale.max);
- } else if (yScale.min > 0 && yScale.max > 0) {
- scaleBase = yScale.getPixelForValue(yScale.min);
- } else {
- scaleBase = yScale.getPixelForValue(0);
- }
- // Update Points
- helpers.each(points, function (point, index) {
- this.updateElement(point, index, reset);
- }, this);
- },
- updateElement: function (point, index, reset) {
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var scaleBase;
- if (yScale.min < 0 && yScale.max < 0) {
- scaleBase = yScale.getPixelForValue(yScale.max);
- } else if (yScale.min > 0 && yScale.max > 0) {
- scaleBase = yScale.getPixelForValue(yScale.min);
- } else {
- scaleBase = yScale.getPixelForValue(0);
- }
- helpers.extend(point, {
- // Utility
- _chart: this.chart.chart,
- _xScale: xScale,
- _yScale: yScale,
- _datasetIndex: this.index,
- _index: index,
- // Desired view properties
- _model: {
- x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
- y: reset ? scaleBase : yScale.getPixelForValue(this.getDataset().data[index], index, this.index),
- // Appearance
- radius: reset ? 0 : point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[index]),
- backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor),
- borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor),
- borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth),
- // Tooltip
- hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius),
- },
- });
- point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
- point.pivot();
- },
- getRadius: function (value) {
- return value.r || this.chart.options.elements.point.radius;
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- // Transition and Draw the Points
- helpers.each(this.getDataset().metaData, function (point, index) {
- point.transition(easingDecimal);
- point.draw();
- }, this);
- },
- setHoverStyle: function (point) {
- // Point
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, this.chart.options.elements.point.hoverRadius)) + this.getRadius(this.getDataset().data[point._index]);
- point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, point._model.borderWidth);
- },
- removeHoverStyle: function (point) {
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[point._index]);
- point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor);
- point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor);
- point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth);
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- //Cache a local reference to Chart.helpers
- helpers = Chart.helpers;
- Chart.defaults.doughnut = {
- animation: {
- //Boolean - Whether we animate the rotation of the Doughnut
- animateRotate: true,
- //Boolean - Whether we animate scaling the Doughnut from the centre
- animateScale: false,
- },
- aspectRatio: 1,
- hover: {
- mode: 'single'
- },
- legendCallback: function (chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
- if (chart.data.datasets.length) {
- for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
- text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
- if (chart.data.labels[i]) {
- text.push(chart.data.labels[i]);
- }
- text.push('</span></li>');
- }
- }
- text.push('</ul>');
- return text.join("");
- },
- legend: {
- labels: {
- generateLabels: function (data) {
- return data.labels.map(function (label, i) {
- return {
- text: label,
- fillStyle: data.datasets[0].backgroundColor[i],
- hidden: isNaN(data.datasets[0].data[i]),
- // Extra data used for toggling the correct item
- index: i
- };
- });
- }
- },
- onClick: function (e, legendItem) {
- helpers.each(this.chart.data.datasets, function (dataset) {
- dataset.metaHiddenData = dataset.metaHiddenData || [];
- var idx = legendItem.index;
- if (!isNaN(dataset.data[idx])) {
- dataset.metaHiddenData[idx] = dataset.data[idx];
- dataset.data[idx] = NaN;
- } else if (!isNaN(dataset.metaHiddenData[idx])) {
- dataset.data[idx] = dataset.metaHiddenData[idx];
- }
- });
- this.chart.update();
- }
- },
- //The percentage of the chart that we cut out of the middle.
- cutoutPercentage: 50,
- // Need to override these to give a nice default
- tooltips: {
- callbacks: {
- title: function () {
- return '';
- },
- label: function (tooltipItem, data) {
- return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
- }
- }
- }
- };
- Chart.defaults.pie = helpers.clone(Chart.defaults.doughnut);
- helpers.extend(Chart.defaults.pie, {
- cutoutPercentage: 0
- });
- Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
- linkScales: function () {
- // no scales for doughnut
- },
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- }, this);
- },
- addElementAndReset: function (index, colorForNewElement) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var arc = new Chart.elements.Arc({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) {
- this.getDataset().backgroundColor.splice(index, 0, colorForNewElement);
- }
- // Reset the point
- this.updateElement(arc, index, true);
- // Add to the points array
- this.getDataset().metaData.splice(index, 0, arc);
- },
- getVisibleDatasetCount: function getVisibleDatasetCount() {
- return helpers.where(this.chart.data.datasets, function (ds) {
- return helpers.isDatasetVisible(ds);
- }).length;
- },
- // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
- getRingIndex: function getRingIndex(datasetIndex) {
- var ringIndex = 0;
- for (var j = 0; j < datasetIndex; ++j) {
- if (helpers.isDatasetVisible(this.chart.data.datasets[j])) {
- ++ringIndex;
- }
- }
- return ringIndex;
- },
- update: function update(reset) {
- var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top);
- this.chart.outerRadius = Math.max((minSize / 2) - this.chart.options.elements.arc.borderWidth / 2, 0);
- this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
- this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
- this.getDataset().total = 0;
- helpers.each(this.getDataset().data, function (value) {
- if (!isNaN(value)) {
- this.getDataset().total += Math.abs(value);
- }
- }, this);
- this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.getRingIndex(this.index));
- this.innerRadius = this.outerRadius - this.chart.radiusLength;
- helpers.each(this.getDataset().metaData, function (arc, index) {
- this.updateElement(arc, index, reset);
- }, this);
- },
- updateElement: function (arc, index, reset) {
- var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
- var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
- var resetModel = {
- x: centerX,
- y: centerY,
- startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
- endAngle: Math.PI * -0.5,
- circumference: (this.chart.options.animation.animateRotate) ? 0 : this.calculateCircumference(this.getDataset().data[index]),
- outerRadius: (this.chart.options.animation.animateScale) ? 0 : this.outerRadius,
- innerRadius: (this.chart.options.animation.animateScale) ? 0 : this.innerRadius
- };
- helpers.extend(arc, {
- // Utility
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- // Desired view properties
- _model: reset ? resetModel : {
- x: centerX,
- y: centerY,
- circumference: this.calculateCircumference(this.getDataset().data[index]),
- outerRadius: this.outerRadius,
- innerRadius: this.innerRadius,
- backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
- hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor),
- borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
- borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
- label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index])
- },
- });
- if (!reset) {
- if (index === 0) {
- arc._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
- } else {
- arc._model.startAngle = this.getDataset().metaData[index - 1]._model.endAngle;
- }
- arc._model.endAngle = arc._model.startAngle + arc._model.circumference;
- //Check to see if it's the last arc, if not get the next and update its start angle
- if (index < this.getDataset().data.length - 1) {
- this.getDataset().metaData[index + 1]._model.startAngle = arc._model.endAngle;
- }
- }
- arc.pivot();
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- helpers.each(this.getDataset().metaData, function (arc, index) {
- arc.transition(easingDecimal).draw();
- }, this);
- },
- setHoverStyle: function (arc) {
- var dataset = this.chart.data.datasets[arc._datasetIndex];
- var index = arc._index;
- arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth);
- },
- removeHoverStyle: function (arc) {
- var dataset = this.chart.data.datasets[arc._datasetIndex];
- var index = arc._index;
- arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor);
- arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor);
- arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth);
- },
- calculateCircumference: function (value) {
- if (this.getDataset().total > 0 && !isNaN(value)) {
- return (Math.PI * 1.999999) * (value / this.getDataset().total);
- } else {
- return 0;
- }
- },
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.line = {
- hover: {
- mode: "label"
- },
- scales: {
- xAxes: [{
- type: "category",
- id: 'x-axis-0'
- }],
- yAxes: [{
- type: "linear",
- id: 'y-axis-0'
- }],
- },
- };
- Chart.controllers.line = Chart.DatasetController.extend({
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _points: this.getDataset().metaData,
- });
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- }, this);
- },
- addElementAndReset: function (index) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var point = new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- // Reset the point
- this.updateElement(point, index, true);
- // Add to the points array
- this.getDataset().metaData.splice(index, 0, point);
- // Make sure bezier control points are updated
- this.updateBezierControlPoints();
- },
- update: function update(reset) {
- var line = this.getDataset().metaDataset;
- var points = this.getDataset().metaData;
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var scaleBase;
- if (yScale.min < 0 && yScale.max < 0) {
- scaleBase = yScale.getPixelForValue(yScale.max);
- } else if (yScale.min > 0 && yScale.max > 0) {
- scaleBase = yScale.getPixelForValue(yScale.min);
- } else {
- scaleBase = yScale.getPixelForValue(0);
- }
- // Update Line
- helpers.extend(line, {
- // Utility
- _scale: yScale,
- _datasetIndex: this.index,
- // Data
- _children: points,
- // Model
- _model: {
- // Appearance
- tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
- backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
- borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
- borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
- borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
- borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
- borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
- borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
- fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
- // Scale
- scaleTop: yScale.top,
- scaleBottom: yScale.bottom,
- scaleZero: scaleBase,
- },
- });
- line.pivot();
- // Update Points
- helpers.each(points, function (point, index) {
- this.updateElement(point, index, reset);
- }, this);
- this.updateBezierControlPoints();
- },
- getPointBackgroundColor: function (point, index) {
- var backgroundColor = this.chart.options.elements.point.backgroundColor;
- var dataset = this.getDataset();
- if (point.custom && point.custom.backgroundColor) {
- backgroundColor = point.custom.backgroundColor;
- } else if (dataset.pointBackgroundColor) {
- backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
- } else if (dataset.backgroundColor) {
- backgroundColor = dataset.backgroundColor;
- }
- return backgroundColor;
- },
- getPointBorderColor: function (point, index) {
- var borderColor = this.chart.options.elements.point.borderColor;
- var dataset = this.getDataset();
- if (point.custom && point.custom.borderColor) {
- borderColor = point.custom.borderColor;
- } else if (dataset.pointBorderColor) {
- borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor);
- } else if (dataset.borderColor) {
- borderColor = dataset.borderColor;
- }
- return borderColor;
- },
- getPointBorderWidth: function (point, index) {
- var borderWidth = this.chart.options.elements.point.borderWidth;
- var dataset = this.getDataset();
- if (point.custom && point.custom.borderWidth !== undefined) {
- borderWidth = point.custom.borderWidth;
- } else if (dataset.pointBorderWidth !== undefined) {
- borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
- } else if (dataset.borderWidth !== undefined) {
- borderWidth = dataset.borderWidth;
- }
- return borderWidth;
- },
- updateElement: function (point, index, reset) {
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var scaleBase;
- if (yScale.min < 0 && yScale.max < 0) {
- scaleBase = yScale.getPixelForValue(yScale.max);
- } else if (yScale.min > 0 && yScale.max > 0) {
- scaleBase = yScale.getPixelForValue(yScale.min);
- } else {
- scaleBase = yScale.getPixelForValue(0);
- }
- helpers.extend(point, {
- // Utility
- _chart: this.chart.chart,
- _xScale: xScale,
- _yScale: yScale,
- _datasetIndex: this.index,
- _index: index,
- // Desired view properties
- _model: {
- x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
- y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
- // Appearance
- tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
- radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
- backgroundColor: this.getPointBackgroundColor(point, index),
- borderColor: this.getPointBorderColor(point, index),
- borderWidth: this.getPointBorderWidth(point, index),
- // Tooltip
- hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius),
- },
- });
- point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
- },
- calculatePointY: function (value, index, datasetIndex, isCombo) {
- var xScale = this.getScaleForId(this.getDataset().xAxisID);
- var yScale = this.getScaleForId(this.getDataset().yAxisID);
- if (yScale.options.stacked) {
- var sumPos = 0,
- sumNeg = 0;
- for (var i = this.chart.data.datasets.length - 1; i > datasetIndex; i--) {
- var ds = this.chart.data.datasets[i];
- if (ds.type === 'line' && helpers.isDatasetVisible(ds)) {
- if (ds.data[index] < 0) {
- sumNeg += ds.data[index] || 0;
- } else {
- sumPos += ds.data[index] || 0;
- }
- }
- }
- if (value < 0) {
- return yScale.getPixelForValue(sumNeg + value);
- } else {
- return yScale.getPixelForValue(sumPos + value);
- }
- return yScale.getPixelForValue(value);
- }
- return yScale.getPixelForValue(value);
- },
- updateBezierControlPoints: function () {
- // Update bezier control points
- helpers.each(this.getDataset().metaData, function (point, index) {
- var controlPoints = helpers.splineCurve(
- helpers.previousItem(this.getDataset().metaData, index)._model,
- point._model,
- helpers.nextItem(this.getDataset().metaData, index)._model,
- point._model.tension
- );
- // Prevent the bezier going outside of the bounds of the graph
- point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
- point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
- point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
- point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
- // Now pivot the point for animation
- point.pivot();
- }, this);
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- // Transition Point Locations
- helpers.each(this.getDataset().metaData, function (point, index) {
- point.transition(easingDecimal);
- }, this);
- // Transition and Draw the line
- this.getDataset().metaDataset.transition(easingDecimal).draw();
- // Draw the points
- helpers.each(this.getDataset().metaData, function (point) {
- point.draw();
- });
- },
- setHoverStyle: function (point) {
- // Point
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
- point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
- },
- removeHoverStyle: function (point) {
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
- point._model.backgroundColor = this.getPointBackgroundColor(point, index);
- point._model.borderColor = this.getPointBorderColor(point, index);
- point._model.borderWidth = this.getPointBorderWidth(point, index);
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- //Cache a local reference to Chart.helpers
- helpers = Chart.helpers;
- Chart.defaults.polarArea = {
- scale: {
- type: "radialLinear",
- lineArc: true, // so that lines are circular
- },
- //Boolean - Whether to animate the rotation of the chart
- animateRotate: true,
- animateScale: true,
- aspectRatio: 1,
- legendCallback: function (chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
- if (chart.data.datasets.length) {
- for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
- text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
- if (chart.data.labels[i]) {
- text.push(chart.data.labels[i]);
- }
- text.push('</span></li>');
- }
- }
- text.push('</ul>');
- return text.join("");
- },
- legend: {
- labels: {
- generateLabels: function (data) {
- return data.labels.map(function (label, i) {
- return {
- text: label,
- fillStyle: data.datasets[0].backgroundColor[i],
- hidden: isNaN(data.datasets[0].data[i]),
- // Extra data used for toggling the correct item
- index: i
- };
- });
- }
- },
- onClick: function (e, legendItem) {
- helpers.each(this.chart.data.datasets, function (dataset) {
- dataset.metaHiddenData = dataset.metaHiddenData || [];
- var idx = legendItem.index;
- if (!isNaN(dataset.data[idx])) {
- dataset.metaHiddenData[idx] = dataset.data[idx];
- dataset.data[idx] = NaN;
- } else if (!isNaN(dataset.metaHiddenData[idx])) {
- dataset.data[idx] = dataset.metaHiddenData[idx];
- }
- });
- this.chart.update();
- }
- },
- // Need to override these to give a nice default
- tooltips: {
- callbacks: {
- title: function () {
- return '';
- },
- label: function (tooltipItem, data) {
- return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
- }
- }
- }
- };
- Chart.controllers.polarArea = Chart.DatasetController.extend({
- linkScales: function () {
- // no scales for doughnut
- },
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- }, this);
- },
- addElementAndReset: function (index) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var arc = new Chart.elements.Arc({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- // Reset the point
- this.updateElement(arc, index, true);
- // Add to the points array
- this.getDataset().metaData.splice(index, 0, arc);
- },
- getVisibleDatasetCount: function getVisibleDatasetCount() {
- return helpers.where(this.chart.data.datasets, function (ds) {
- return helpers.isDatasetVisible(ds);
- }).length;
- },
- update: function update(reset) {
- var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top);
- this.chart.outerRadius = Math.max((minSize - this.chart.options.elements.arc.borderWidth / 2) / 2, 0);
- this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
- this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
- this.getDataset().total = 0;
- helpers.each(this.getDataset().data, function (value) {
- this.getDataset().total += Math.abs(value);
- }, this);
- this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.index);
- this.innerRadius = this.outerRadius - this.chart.radiusLength;
- helpers.each(this.getDataset().metaData, function (arc, index) {
- this.updateElement(arc, index, reset);
- }, this);
- },
- updateElement: function (arc, index, reset) {
- var circumference = this.calculateCircumference(this.getDataset().data[index]);
- var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
- var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
- // If there is NaN data before us, we need to calculate the starting angle correctly.
- // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
- var notNullIndex = 0;
- for (var i = 0; i < index; ++i) {
- if (!isNaN(this.getDataset().data[i])) {
- ++notNullIndex;
- }
- }
- var startAngle = (-0.5 * Math.PI) + (circumference * notNullIndex);
- var endAngle = startAngle + circumference;
- var resetModel = {
- x: centerX,
- y: centerY,
- innerRadius: 0,
- outerRadius: this.chart.options.animateScale ? 0 : this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
- startAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : startAngle,
- endAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : endAngle,
- backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
- hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor),
- borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
- borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
- label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index])
- };
- helpers.extend(arc, {
- // Utility
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- _scale: this.chart.scale,
- // Desired view properties
- _model: reset ? resetModel : {
- x: centerX,
- y: centerY,
- innerRadius: 0,
- outerRadius: this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
- startAngle: startAngle,
- endAngle: endAngle,
- backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
- hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor),
- borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
- borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
- label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index])
- },
- });
- arc.pivot();
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- helpers.each(this.getDataset().metaData, function (arc, index) {
- arc.transition(easingDecimal).draw();
- }, this);
- },
- setHoverStyle: function (arc) {
- var dataset = this.chart.data.datasets[arc._datasetIndex];
- var index = arc._index;
- arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, arc._model.borderWidth);
- },
- removeHoverStyle: function (arc) {
- var dataset = this.chart.data.datasets[arc._datasetIndex];
- var index = arc._index;
- arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor);
- arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor);
- arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth);
- },
- calculateCircumference: function (value) {
- if (isNaN(value)) {
- return 0;
- } else {
- // Count the number of NaN values
- var numNaN = helpers.where(this.getDataset().data, function (data) {
- return isNaN(data);
- }).length;
- return (2 * Math.PI) / (this.getDataset().data.length - numNaN);
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.radar = {
- scale: {
- type: "radialLinear",
- },
- elements: {
- line: {
- tension: 0, // no bezier in radar
- }
- },
- };
- Chart.controllers.radar = Chart.DatasetController.extend({
- linkScales: function () {
- // No need. Single scale only
- },
- addElements: function () {
- this.getDataset().metaData = this.getDataset().metaData || [];
- this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _points: this.getDataset().metaData,
- _loop: true
- });
- helpers.each(this.getDataset().data, function (value, index) {
- this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- _model: {
- x: 0, //xScale.getPixelForValue(null, index, true),
- y: 0, //this.chartArea.bottom,
- },
- });
- }, this);
- },
- addElementAndReset: function (index) {
- this.getDataset().metaData = this.getDataset().metaData || [];
- var point = new Chart.elements.Point({
- _chart: this.chart.chart,
- _datasetIndex: this.index,
- _index: index,
- });
- // Reset the point
- this.updateElement(point, index, true);
- // Add to the points array
- this.getDataset().metaData.splice(index, 0, point);
- // Make sure bezier control points are updated
- this.updateBezierControlPoints();
- },
- update: function update(reset) {
- var line = this.getDataset().metaDataset;
- var points = this.getDataset().metaData;
- var scale = this.chart.scale;
- var scaleBase;
- if (scale.min < 0 && scale.max < 0) {
- scaleBase = scale.getPointPositionForValue(0, scale.max);
- } else if (scale.min > 0 && scale.max > 0) {
- scaleBase = scale.getPointPositionForValue(0, scale.min);
- } else {
- scaleBase = scale.getPointPositionForValue(0, 0);
- }
- helpers.extend(this.getDataset().metaDataset, {
- // Utility
- _datasetIndex: this.index,
- // Data
- _children: this.getDataset().metaData,
- // Model
- _model: {
- // Appearance
- tension: helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
- backgroundColor: this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor,
- borderWidth: this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth,
- borderColor: this.getDataset().borderColor || this.chart.options.elements.line.borderColor,
- fill: this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill, // use the value from the this.getDataset() if it was provided. else fall back to the default
- // Scale
- scaleTop: scale.top,
- scaleBottom: scale.bottom,
- scaleZero: scaleBase,
- },
- });
- this.getDataset().metaDataset.pivot();
- // Update Points
- helpers.each(points, function (point, index) {
- this.updateElement(point, index, reset);
- }, this);
- // Update bezier control points
- this.updateBezierControlPoints();
- },
- updateElement: function (point, index, reset) {
- var pointPosition = this.chart.scale.getPointPositionForValue(index, this.getDataset().data[index]);
- helpers.extend(point, {
- // Utility
- _datasetIndex: this.index,
- _index: index,
- _scale: this.chart.scale,
- // Desired view properties
- _model: {
- x: reset ? this.chart.scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
- y: reset ? this.chart.scale.yCenter : pointPosition.y,
- // Appearance
- tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
- radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, index, this.chart.options.elements.point.radius),
- backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor),
- borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor),
- borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth),
- // Tooltip
- hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius),
- },
- });
- point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
- },
- updateBezierControlPoints: function () {
- helpers.each(this.getDataset().metaData, function (point, index) {
- var controlPoints = helpers.splineCurve(
- helpers.previousItem(this.getDataset().metaData, index, true)._model,
- point._model,
- helpers.nextItem(this.getDataset().metaData, index, true)._model,
- point._model.tension
- );
- // Prevent the bezier going outside of the bounds of the graph
- point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
- point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
- point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
- point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
- // Now pivot the point for animation
- point.pivot();
- }, this);
- },
- draw: function (ease) {
- var easingDecimal = ease || 1;
- // Transition Point Locations
- helpers.each(this.getDataset().metaData, function (point, index) {
- point.transition(easingDecimal);
- }, this);
- // Transition and Draw the line
- this.getDataset().metaDataset.transition(easingDecimal).draw();
- // Draw the points
- helpers.each(this.getDataset().metaData, function (point) {
- point.draw();
- });
- },
- setHoverStyle: function (point) {
- // Point
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
- point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
- point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth);
- },
- removeHoverStyle: function (point) {
- var dataset = this.chart.data.datasets[point._datasetIndex];
- var index = point._index;
- point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
- point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor);
- point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor);
- point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth);
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- // Default config for a category scale
- var defaultConfig = {
- position: "bottom",
- };
- var DatasetScale = Chart.Scale.extend({
- buildTicks: function (index) {
- this.ticks = this.chart.data.labels;
- },
- getLabelForIndex: function (index, datasetIndex) {
- return this.ticks[index];
- },
- // Used to get data value locations. Value can either be an index or a numerical value
- getPixelForValue: function (value, index, datasetIndex, includeOffset) {
- if (this.isHorizontal()) {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- var valueWidth = innerWidth / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
- var widthOffset = (valueWidth * index) + this.paddingLeft;
- if (this.options.gridLines.offsetGridLines && includeOffset) {
- widthOffset += (valueWidth / 2);
- }
- return this.left + Math.round(widthOffset);
- } else {
- var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
- var valueHeight = innerHeight / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
- var heightOffset = (valueHeight * index) + this.paddingTop;
- if (this.options.gridLines.offsetGridLines && includeOffset) {
- heightOffset += (valueHeight / 2);
- }
- return this.top + Math.round(heightOffset);
- }
- },
- });
- Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- var defaultConfig = {
- position: "left",
- ticks: {
- callback: function (tickValue, index, ticks) {
- var delta = ticks[1] - ticks[0];
- // If we have a number like 2.5 as the delta, figure out how many decimal places we need
- if (Math.abs(delta) > 1) {
- if (tickValue !== Math.floor(tickValue)) {
- // not an integer
- delta = tickValue - Math.floor(tickValue);
- }
- }
- var logDelta = helpers.log10(Math.abs(delta));
- var tickString = '';
- if (tickValue !== 0) {
- var numDecimal = -1 * Math.floor(logDelta);
- numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
- tickString = tickValue.toFixed(numDecimal);
- } else {
- tickString = '0'; // never show decimal places for 0
- }
- return tickString;
- }
- }
- };
- var LinearScale = Chart.Scale.extend({
- determineDataLimits: function () {
- // First Calculate the range
- this.min = null;
- this.max = null;
- if (this.options.stacked) {
- var valuesPerType = {};
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (valuesPerType[dataset.type] === undefined) {
- valuesPerType[dataset.type] = {
- positiveValues: [],
- negativeValues: [],
- };
- }
- // Store these per type
- var positiveValues = valuesPerType[dataset.type].positiveValues;
- var negativeValues = valuesPerType[dataset.type].negativeValues;
- if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
- helpers.each(dataset.data, function (rawValue, index) {
- var value = +this.getRightValue(rawValue);
- if (isNaN(value)) {
- return;
- }
- positiveValues[index] = positiveValues[index] || 0;
- negativeValues[index] = negativeValues[index] || 0;
- if (this.options.relativePoints) {
- positiveValues[index] = 100;
- } else {
- if (value < 0) {
- negativeValues[index] += value;
- } else {
- positiveValues[index] += value;
- }
- }
- }, this);
- }
- }, this);
- helpers.each(valuesPerType, function (valuesForType) {
- var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
- var minVal = helpers.min(values);
- var maxVal = helpers.max(values);
- this.min = this.min === null ? minVal : Math.min(this.min, minVal);
- this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
- }, this);
- } else {
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
- helpers.each(dataset.data, function (rawValue, index) {
- var value = +this.getRightValue(rawValue);
- if (isNaN(value)) {
- return;
- }
- if (this.min === null) {
- this.min = value;
- } else if (value < this.min) {
- this.min = value;
- }
- if (this.max === null) {
- this.max = value;
- } else if (value > this.max) {
- this.max = value;
- }
- }, this);
- }
- }, this);
- }
- // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
- // do nothing since that would make the chart weird. If the user really wants a weird chart
- // axis, they can manually override it
- if (this.options.ticks.beginAtZero) {
- var minSign = helpers.sign(this.min);
- var maxSign = helpers.sign(this.max);
- if (minSign < 0 && maxSign < 0) {
- // move the top up to 0
- this.max = 0;
- } else if (minSign > 0 && maxSign > 0) {
- // move the botttom down to 0
- this.min = 0;
- }
- }
- if (this.options.ticks.min !== undefined) {
- this.min = this.options.ticks.min;
- } else if (this.options.ticks.suggestedMin !== undefined) {
- this.min = Math.min(this.min, this.options.ticks.suggestedMin);
- }
- if (this.options.ticks.max !== undefined) {
- this.max = this.options.ticks.max;
- } else if (this.options.ticks.suggestedMax !== undefined) {
- this.max = Math.max(this.max, this.options.ticks.suggestedMax);
- }
- if (this.min === this.max) {
- this.min--;
- this.max++;
- }
- },
- buildTicks: function () {
- // Then calulate the ticks
- this.ticks = [];
- // Figure out what the max number of ticks we can support it is based on the size of
- // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
- // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
- // the graph
- var maxTicks;
- if (this.isHorizontal()) {
- maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
- Math.ceil(this.width / 50));
- } else {
- // The factor of 2 used to scale the font size has been experimentally determined.
- maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
- Math.ceil(this.height / (2 * this.options.ticks.fontSize)));
- }
- // Make sure we always have at least 2 ticks
- maxTicks = Math.max(2, maxTicks);
- // To get a "nice" value for the tick spacing, we will use the appropriately named
- // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
- // for details.
- var niceRange = helpers.niceNum(this.max - this.min, false);
- var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
- var niceMin = Math.floor(this.min / spacing) * spacing;
- var niceMax = Math.ceil(this.max / spacing) * spacing;
- var numSpaces = Math.ceil((niceMax - niceMin) / spacing);
- // Put the values into the ticks array
- this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
- for (var j = 1; j < numSpaces; ++j) {
- this.ticks.push(niceMin + (j * spacing));
- }
- this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
- if (this.options.position == "left" || this.options.position == "right") {
- // We are in a vertical orientation. The top value is the highest. So reverse the array
- this.ticks.reverse();
- }
- // At this point, we need to update our max and min given the tick values since we have expanded the
- // range of the scale
- this.max = helpers.max(this.ticks);
- this.min = helpers.min(this.ticks);
- this.ticksAsNumbers = this.ticks.slice();
- if (this.options.ticks.reverse) {
- this.ticks.reverse();
- this.start = this.max;
- this.end = this.min;
- } else {
- this.start = this.min;
- this.end = this.max;
- }
- this.zeroLineIndex = this.ticks.indexOf(0);
- },
- getLabelForIndex: function (index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
- },
- // Utils
- getPixelForValue: function (value, index, datasetIndex, includeOffset) {
- // This must be called after fit has been run so that
- // this.left, this.top, this.right, and this.bottom have been defined
- var rightValue = +this.getRightValue(value);
- var pixel;
- var range = this.end - this.start;
- if (this.isHorizontal()) {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- pixel = this.left + (innerWidth / range * (rightValue - this.start));
- return Math.round(pixel + this.paddingLeft);
- } else {
- var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
- pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (rightValue - this.start));
- return Math.round(pixel);
- }
- },
- getPixelForTick: function (index, includeOffset) {
- return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset);
- },
- });
- Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- var defaultConfig = {
- position: "left",
- // label settings
- ticks: {
- callback: function (value, index, arr) {
- var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value))));
- if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
- return value.toExponential();
- } else {
- return '';
- }
- }
- }
- };
- var LogarithmicScale = Chart.Scale.extend({
- determineDataLimits: function () {
- // Calculate Range
- this.min = null;
- this.max = null;
- if (this.options.stacked) {
- var valuesPerType = {};
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
- if (valuesPerType[dataset.type] === undefined) {
- valuesPerType[dataset.type] = [];
- }
- helpers.each(dataset.data, function (rawValue, index) {
- var values = valuesPerType[dataset.type];
- var value = +this.getRightValue(rawValue);
- if (isNaN(value)) {
- return;
- }
- values[index] = values[index] || 0;
- if (this.options.relativePoints) {
- values[index] = 100;
- } else {
- // Don't need to split positive and negative since the log scale can't handle a 0 crossing
- values[index] += value;
- }
- }, this);
- }
- }, this);
- helpers.each(valuesPerType, function (valuesForType) {
- var minVal = helpers.min(valuesForType);
- var maxVal = helpers.max(valuesForType);
- this.min = this.min === null ? minVal : Math.min(this.min, minVal);
- this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
- }, this);
- } else {
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
- helpers.each(dataset.data, function (rawValue, index) {
- var value = +this.getRightValue(rawValue);
- if (isNaN(value)) {
- return;
- }
- if (this.min === null) {
- this.min = value;
- } else if (value < this.min) {
- this.min = value;
- }
- if (this.max === null) {
- this.max = value;
- } else if (value > this.max) {
- this.max = value;
- }
- }, this);
- }
- }, this);
- }
- this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min;
- this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max;
- if (this.min === this.max) {
- if (this.min !== 0 && this.min !== null) {
- this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1);
- this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1);
- } else {
- this.min = 1;
- this.max = 10;
- }
- }
- },
- buildTicks: function () {
- // Reset the ticks array. Later on, we will draw a grid line at these positions
- // The array simply contains the numerical value of the spots where ticks will be
- this.tickValues = [];
- // Figure out what the max number of ticks we can support it is based on the size of
- // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
- // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
- // the graph
- var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min)));
- while (tickVal < this.max) {
- this.tickValues.push(tickVal);
- var exp = Math.floor(helpers.log10(tickVal));
- var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
- if (significand === 10) {
- significand = 1;
- ++exp;
- }
- tickVal = significand * Math.pow(10, exp);
- }
- var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal;
- this.tickValues.push(lastTick);
- if (this.options.position == "left" || this.options.position == "right") {
- // We are in a vertical orientation. The top value is the highest. So reverse the array
- this.tickValues.reverse();
- }
- // At this point, we need to update our max and min given the tick values since we have expanded the
- // range of the scale
- this.max = helpers.max(this.tickValues);
- this.min = helpers.min(this.tickValues);
- if (this.options.ticks.reverse) {
- this.tickValues.reverse();
- this.start = this.max;
- this.end = this.min;
- } else {
- this.start = this.min;
- this.end = this.max;
- }
- this.ticks = this.tickValues.slice();
- },
- // Get the correct tooltip label
- getLabelForIndex: function (index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
- },
- getPixelForTick: function (index, includeOffset) {
- return this.getPixelForValue(this.tickValues[index], null, null, includeOffset);
- },
- getPixelForValue: function (value, index, datasetIndex, includeOffset) {
- var pixel;
- var newVal = +this.getRightValue(value);
- var range = helpers.log10(this.end) - helpers.log10(this.start);
- if (this.isHorizontal()) {
- if (newVal === 0) {
- pixel = this.left + this.paddingLeft;
- } else {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start)));
- return pixel + this.paddingLeft;
- }
- } else {
- // Bottom - top since pixels increase downard on a screen
- if (newVal === 0) {
- pixel = this.top + this.paddingTop;
- } else {
- var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
- return (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start)));
- }
- }
- },
- });
- Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- var defaultConfig = {
- display: true,
- //Boolean - Whether to animate scaling the chart from the centre
- animate: true,
- lineArc: false,
- position: "chartArea",
- angleLines: {
- display: true,
- color: "rgba(0, 0, 0, 0.1)",
- lineWidth: 1
- },
- // label settings
- ticks: {
- //Boolean - Show a backdrop to the scale label
- showLabelBackdrop: true,
- //String - The colour of the label backdrop
- backdropColor: "rgba(255,255,255,0.75)",
- //Number - The backdrop padding above & below the label in pixels
- backdropPaddingY: 2,
- //Number - The backdrop padding to the side of the label in pixels
- backdropPaddingX: 2,
- },
- pointLabels: {
- //String - Point label font declaration
- fontFamily: "'Arial'",
- //String - Point label font weight
- fontStyle: "normal",
- //Number - Point label font size in pixels
- fontSize: 10,
- //String - Point label font colour
- fontColor: "#666",
- },
- };
- var LinearRadialScale = Chart.Scale.extend({
- getValueCount: function () {
- return this.chart.data.labels.length;
- },
- setDimensions: function () {
- // Set the unconstrained dimension before label rotation
- this.width = this.maxWidth;
- this.height = this.maxHeight;
- this.xCenter = Math.round(this.width / 2);
- this.yCenter = Math.round(this.height / 2);
- var minSize = helpers.min([this.height, this.width]);
- this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2);
- },
- determineDataLimits: function () {
- this.min = null;
- this.max = null;
- helpers.each(this.chart.data.datasets, function (dataset) {
- if (helpers.isDatasetVisible(dataset)) {
- helpers.each(dataset.data, function (rawValue, index) {
- var value = +this.getRightValue(rawValue);
- if (isNaN(value)) {
- return;
- }
- if (this.min === null) {
- this.min = value;
- } else if (value < this.min) {
- this.min = value;
- }
- if (this.max === null) {
- this.max = value;
- } else if (value > this.max) {
- this.max = value;
- }
- }, this);
- }
- }, this);
- // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
- // do nothing since that would make the chart weird. If the user really wants a weird chart
- // axis, they can manually override it
- if (this.options.ticks.beginAtZero) {
- var minSign = helpers.sign(this.min);
- var maxSign = helpers.sign(this.max);
- if (minSign < 0 && maxSign < 0) {
- // move the top up to 0
- this.max = 0;
- } else if (minSign > 0 && maxSign > 0) {
- // move the botttom down to 0
- this.min = 0;
- }
- }
- if (this.options.ticks.min !== undefined) {
- this.min = this.options.ticks.min;
- } else if (this.options.ticks.suggestedMin !== undefined) {
- this.min = Math.min(this.min, this.options.ticks.suggestedMin);
- }
- if (this.options.ticks.max !== undefined) {
- this.max = this.options.ticks.max;
- } else if (this.options.ticks.suggestedMax !== undefined) {
- this.max = Math.max(this.max, this.options.ticks.suggestedMax);
- }
- if (this.min === this.max) {
- this.min--;
- this.max++;
- }
- },
- buildTicks: function () {
- this.ticks = [];
- // Figure out what the max number of ticks we can support it is based on the size of
- // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
- // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
- // the graph
- var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
- Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize)));
- maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks
- // To get a "nice" value for the tick spacing, we will use the appropriately named
- // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
- // for details.
- var niceRange = helpers.niceNum(this.max - this.min, false);
- var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
- var niceMin = Math.floor(this.min / spacing) * spacing;
- var niceMax = Math.ceil(this.max / spacing) * spacing;
- var numSpaces = Math.ceil((niceMax - niceMin) / spacing);
- // Put the values into the ticks array
- this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
- for (var j = 1; j < numSpaces; ++j) {
- this.ticks.push(niceMin + (j * spacing));
- }
- this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
- // At this point, we need to update our max and min given the tick values since we have expanded the
- // range of the scale
- this.max = helpers.max(this.ticks);
- this.min = helpers.min(this.ticks);
- if (this.options.ticks.reverse) {
- this.ticks.reverse();
- this.start = this.max;
- this.end = this.min;
- } else {
- this.start = this.min;
- this.end = this.max;
- }
- this.zeroLineIndex = this.ticks.indexOf(0);
- },
- getLabelForIndex: function (index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
- },
- getCircumference: function () {
- return ((Math.PI * 2) / this.getValueCount());
- },
- fit: function () {
- /*
- * Right, this is really confusing and there is a lot of maths going on here
- * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
- *
- * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
- *
- * Solution:
- *
- * We assume the radius of the polygon is half the size of the canvas at first
- * at each index we check if the text overlaps.
- *
- * Where it does, we store that angle and that index.
- *
- * After finding the largest index and angle we calculate how much we need to remove
- * from the shape radius to move the point inwards by that x.
- *
- * We average the left and right distances to get the maximum shape radius that can fit in the box
- * along with labels.
- *
- * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
- * on each side, removing that from the size, halving it and adding the left x protrusion width.
- *
- * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
- * and position it in the most space efficient manner
- *
- * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
- */
- // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
- // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
- var largestPossibleRadius = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 5), this.width / 2]),
- pointPosition,
- i,
- textWidth,
- halfTextWidth,
- furthestRight = this.width,
- furthestRightIndex,
- furthestRightAngle,
- furthestLeft = 0,
- furthestLeftIndex,
- furthestLeftAngle,
- xProtrusionLeft,
- xProtrusionRight,
- radiusReductionRight,
- radiusReductionLeft,
- maxWidthRadius;
- this.ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily);
- for (i = 0; i < this.getValueCount(); i++) {
- // 5px to space the text slightly out - similar to what we do in the draw function.
- pointPosition = this.getPointPosition(i, largestPossibleRadius);
- textWidth = this.ctx.measureText(this.options.ticks.callback(this.chart.data.labels[i])).width + 5;
- if (i === 0 || i === this.getValueCount() / 2) {
- // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
- // of the radar chart, so text will be aligned centrally, so we'll half it and compare
- // w/left and right text sizes
- halfTextWidth = textWidth / 2;
- if (pointPosition.x + halfTextWidth > furthestRight) {
- furthestRight = pointPosition.x + halfTextWidth;
- furthestRightIndex = i;
- }
- if (pointPosition.x - halfTextWidth < furthestLeft) {
- furthestLeft = pointPosition.x - halfTextWidth;
- furthestLeftIndex = i;
- }
- } else if (i < this.getValueCount() / 2) {
- // Less than half the values means we'll left align the text
- if (pointPosition.x + textWidth > furthestRight) {
- furthestRight = pointPosition.x + textWidth;
- furthestRightIndex = i;
- }
- } else if (i > this.getValueCount() / 2) {
- // More than half the values means we'll right align the text
- if (pointPosition.x - textWidth < furthestLeft) {
- furthestLeft = pointPosition.x - textWidth;
- furthestLeftIndex = i;
- }
- }
- }
- xProtrusionLeft = furthestLeft;
- xProtrusionRight = Math.ceil(furthestRight - this.width);
- furthestRightAngle = this.getIndexAngle(furthestRightIndex);
- furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
- radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
- radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
- // Ensure we actually need to reduce the size of the chart
- radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
- radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
- this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
- this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
- },
- setCenterPoint: function (leftMovement, rightMovement) {
- var maxRight = this.width - rightMovement - this.drawingArea,
- maxLeft = leftMovement + this.drawingArea;
- this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left);
- // Always vertically in the centre as the text height doesn't change
- this.yCenter = Math.round((this.height / 2) + this.top);
- },
- getIndexAngle: function (index) {
- var angleMultiplier = (Math.PI * 2) / this.getValueCount();
- // Start from the top instead of right, so remove a quarter of the circle
- return index * angleMultiplier - (Math.PI / 2);
- },
- getDistanceFromCenterForValue: function (value) {
- if (value === null) {
- return 0; // null always in center
- }
- // Take into account half font size + the yPadding of the top value
- var scalingFactor = this.drawingArea / (this.max - this.min);
- if (this.options.reverse) {
- return (this.max - value) * scalingFactor;
- } else {
- return (value - this.min) * scalingFactor;
- }
- },
- getPointPosition: function (index, distanceFromCenter) {
- var thisAngle = this.getIndexAngle(index);
- return {
- x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
- y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
- };
- },
- getPointPositionForValue: function (index, value) {
- return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
- },
- draw: function () {
- if (this.options.display) {
- var ctx = this.ctx;
- helpers.each(this.ticks, function (label, index) {
- // Don't draw a centre value (if it is minimum)
- if (index > 0 || this.options.reverse) {
- var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]);
- var yHeight = this.yCenter - yCenterOffset;
- // Draw circular lines around the scale
- if (this.options.gridLines.display) {
- ctx.strokeStyle = this.options.gridLines.color;
- ctx.lineWidth = this.options.gridLines.lineWidth;
- if (this.options.lineArc) {
- // Draw circular arcs between the points
- ctx.beginPath();
- ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
- ctx.closePath();
- ctx.stroke();
- } else {
- // Draw straight lines connecting each index
- ctx.beginPath();
- for (var i = 0; i < this.getValueCount(); i++) {
- var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index]));
- if (i === 0) {
- ctx.moveTo(pointPosition.x, pointPosition.y);
- } else {
- ctx.lineTo(pointPosition.x, pointPosition.y);
- }
- }
- ctx.closePath();
- ctx.stroke();
- }
- }
- if (this.options.ticks.display) {
- ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
- if (this.options.ticks.showLabelBackdrop) {
- var labelWidth = ctx.measureText(label).width;
- ctx.fillStyle = this.options.ticks.backdropColor;
- ctx.fillRect(
- this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX,
- yHeight - this.options.ticks.fontSize / 2 - this.options.ticks.backdropPaddingY,
- labelWidth + this.options.ticks.backdropPaddingX * 2,
- this.options.ticks.fontSize + this.options.ticks.backdropPaddingY * 2
- );
- }
- ctx.textAlign = 'center';
- ctx.textBaseline = "middle";
- ctx.fillStyle = this.options.ticks.fontColor;
- ctx.fillText(label, this.xCenter, yHeight);
- }
- }
- }, this);
- if (!this.options.lineArc) {
- ctx.lineWidth = this.options.angleLines.lineWidth;
- ctx.strokeStyle = this.options.angleLines.color;
- for (var i = this.getValueCount() - 1; i >= 0; i--) {
- if (this.options.angleLines.display) {
- var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max));
- ctx.beginPath();
- ctx.moveTo(this.xCenter, this.yCenter);
- ctx.lineTo(outerPosition.x, outerPosition.y);
- ctx.stroke();
- ctx.closePath();
- }
- // Extra 3px out for some label spacing
- var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5);
- ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily);
- ctx.fillStyle = this.options.pointLabels.fontColor;
- var labelsCount = this.chart.data.labels.length,
- halfLabelsCount = this.chart.data.labels.length / 2,
- quarterLabelsCount = halfLabelsCount / 2,
- upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
- exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
- if (i === 0) {
- ctx.textAlign = 'center';
- } else if (i === halfLabelsCount) {
- ctx.textAlign = 'center';
- } else if (i < halfLabelsCount) {
- ctx.textAlign = 'left';
- } else {
- ctx.textAlign = 'right';
- }
- // Set the correct text baseline based on outer positioning
- if (exactQuarter) {
- ctx.textBaseline = 'middle';
- } else if (upperHalf) {
- ctx.textBaseline = 'bottom';
- } else {
- ctx.textBaseline = 'top';
- }
- ctx.fillText(this.chart.data.labels[i], pointLabelPosition.x, pointLabelPosition.y);
- }
- }
- }
- }
- });
- Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig);
- }).call(this);
- (function (moment) {
- "use strict";
- if (!moment) {
- console.warn('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at http://momentjs.com/');
- return;
- }
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- var time = {
- units: [
- {
- name: 'millisecond',
- steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
- }, {
- name: 'second',
- steps: [1, 2, 5, 10, 30]
- }, {
- name: 'minute',
- steps: [1, 2, 5, 10, 30]
- }, {
- name: 'hour',
- steps: [1, 2, 3, 6, 12]
- }, {
- name: 'day',
- steps: [1, 2, 5]
- }, {
- name: 'week',
- maxStep: 4
- }, {
- name: 'month',
- maxStep: 3
- }, {
- name: 'quarter',
- maxStep: 4,
- }, {
- name: 'year',
- maxStep: false
- },
- ],
- };
- var defaultConfig = {
- position: "bottom",
- time: {
- format: false, // false == date objects or use pattern string from http://momentjs.com/docs/#/parsing/string-format/
- unit: false, // false == automatic or override with week, month, year, etc.
- round: false, // none, or override with week, month, year, etc.
- displayFormat: false, // DEPRECATED
- // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
- displayFormats: {
- 'millisecond': 'SSS [ms]',
- 'second': 'h:mm:ss a', // 11:20:01 AM
- 'minute': 'h:mm:ss a', // 11:20:01 AM
- 'hour': 'MMM D, hA', // Sept 4, 5PM
- 'day': 'll', // Sep 4 2015
- 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ?
- 'month': 'MMM YYYY', // Sept 2015
- 'quarter': '[Q]Q - YYYY', // Q3
- 'year': 'YYYY', // 2015
- },
- },
- };
- var TimeScale = Chart.Scale.extend({
- getLabelMoment: function (datasetIndex, index) {
- return this.labelMoments[datasetIndex][index];
- },
- determineDataLimits: function () {
- this.labelMoments = [];
- // Only parse these once. If the dataset does not have data as x,y pairs, we will use
- // these
- var scaleLabelMoments = [];
- if (this.chart.data.labels && this.chart.data.labels.length > 0) {
- helpers.each(this.chart.data.labels, function (label, index) {
- var labelMoment = this.parseTime(label);
- if (this.options.time.round) {
- labelMoment.startOf(this.options.time.round);
- }
- scaleLabelMoments.push(labelMoment);
- }, this);
- if (this.options.time.min) {
- this.firstTick = this.parseTime(this.options.time.min);
- } else {
- this.firstTick = moment.min.call(this, scaleLabelMoments);
- }
- if (this.options.time.max) {
- this.lastTick = this.parseTime(this.options.time.max);
- } else {
- this.lastTick = moment.max.call(this, scaleLabelMoments);
- }
- } else {
- this.firstTick = null;
- this.lastTick = null;
- }
- helpers.each(this.chart.data.datasets, function (dataset, datasetIndex) {
- var momentsForDataset = [];
- if (typeof dataset.data[0] === 'object') {
- helpers.each(dataset.data, function (value, index) {
- var labelMoment = this.parseTime(this.getRightValue(value));
- if (this.options.time.round) {
- labelMoment.startOf(this.options.time.round);
- }
- momentsForDataset.push(labelMoment);
- // May have gone outside the scale ranges, make sure we keep the first and last ticks updated
- this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment;
- this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment;
- }, this);
- } else {
- // We have no labels. Use the ones from the scale
- momentsForDataset = scaleLabelMoments;
- }
- this.labelMoments.push(momentsForDataset);
- }, this);
- // We will modify these, so clone for later
- this.firstTick = (this.firstTick || moment()).clone();
- this.lastTick = (this.lastTick || moment()).clone();
- },
- buildTicks: function (index) {
- this.ticks = [];
- this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
- // Set unit override if applicable
- if (this.options.time.unit) {
- this.tickUnit = this.options.time.unit || 'day';
- this.displayFormat = this.options.time.displayFormats[this.tickUnit];
- this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
- } else {
- // Determine the smallest needed unit of the time
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- var labelCapacity = innerWidth / (this.options.ticks.fontSize + 10);
- var buffer = this.options.time.round ? 0 : 2;
- // Start as small as possible
- this.tickUnit = 'millisecond';
- this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true) + buffer);
- this.displayFormat = this.options.time.displayFormats[this.tickUnit];
- var unitDefinitionIndex = 0;
- var unitDefinition = time.units[unitDefinitionIndex];
- // While we aren't ideal and we don't have units left
- while (unitDefinitionIndex < time.units.length) {
- // Can we scale this unit. If `false` we can scale infinitely
- //var canScaleUnit = ;
- this.unitScale = 1;
- if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.tickRange / labelCapacity) < helpers.max(unitDefinition.steps)) {
- // Use one of the prefedined steps
- for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
- if (unitDefinition.steps[idx] > Math.ceil(this.tickRange / labelCapacity)) {
- this.unitScale = unitDefinition.steps[idx];
- break;
- }
- }
- break;
- } else if ((unitDefinition.maxStep === false) || (Math.ceil(this.tickRange / labelCapacity) < unitDefinition.maxStep)) {
- // We have a max step. Scale this unit
- this.unitScale = Math.ceil(this.tickRange / labelCapacity);
- break;
- } else {
- // Move to the next unit up
- ++unitDefinitionIndex;
- unitDefinition = time.units[unitDefinitionIndex];
- this.tickUnit = unitDefinition.name;
- this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer);
- this.displayFormat = this.options.time.displayFormats[unitDefinition.name];
- }
- }
- }
- this.firstTick.startOf(this.tickUnit);
- this.lastTick.endOf(this.tickUnit);
- this.smallestLabelSeparation = this.width;
- helpers.each(this.chart.data.datasets, function (dataset, datasetIndex) {
- for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) {
- this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true));
- }
- }, this);
- // Tick displayFormat override
- if (this.options.time.displayFormat) {
- this.displayFormat = this.options.time.displayFormat;
- }
- // For every unit in between the first and last moment, create a moment and add it to the ticks tick
- for (var i = 0; i <= this.tickRange; ++i) {
- if (i % this.unitScale === 0) {
- this.ticks.push(this.firstTick.clone().add(i, this.tickUnit));
- } else if (i === this.tickRange) {
- // Expand out the last one if not an exact multiple
- this.tickRange = Math.ceil(this.tickRange / this.unitScale) * this.unitScale;
- this.ticks.push(this.firstTick.clone().add(this.tickRange, this.tickUnit));
- this.lastTick = this.ticks[this.ticks.length - 1].clone();
- break;
- }
- }
- },
- // Get tooltip label
- getLabelForIndex: function (index, datasetIndex) {
- var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : '';
- if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') {
- label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
- }
- // Format nicely
- if (this.options.time.tooltipFormat) {
- label = this.parseTime(label).format(this.options.time.tooltipFormat);
- }
- return label;
- },
- convertTicksToLabels: function () {
- this.ticks = this.ticks.map(function (tick, index, ticks) {
- var formattedTick = tick.format(this.displayFormat);
- if (this.options.ticks.userCallback) {
- return this.options.ticks.userCallback(formattedTick, index, ticks);
- } else {
- return formattedTick;
- }
- }, this);
- },
- getPixelForValue: function (value, index, datasetIndex, includeOffset) {
- var labelMoment = this.getLabelMoment(datasetIndex, index);
- var offset = labelMoment.diff(this.firstTick, this.tickUnit, true);
- var decimal = offset / this.tickRange;
- if (this.isHorizontal()) {
- var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
- var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
- var valueOffset = (innerWidth * decimal) + this.paddingLeft;
- return this.left + Math.round(valueOffset);
- } else {
- var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
- var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
- var heightOffset = (innerHeight * decimal) + this.paddingTop;
- return this.top + Math.round(heightOffset);
- }
- },
- parseTime: function (label) {
- // Date objects
- if (typeof label.getMonth === 'function' || typeof label == 'number') {
- return moment(label);
- }
- // Moment support
- if (label.isValid && label.isValid()) {
- return label;
- }
- // Custom parsing (return an instance of moment)
- if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
- return this.options.time.format(label);
- }
- // Moment format parsing
- return moment(label, this.options.time.format);
- },
- });
- Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);
- }).call(this, moment);
- /*!
- * Chart.js
- * http://chartjs.org/
- * Version: 2.0.0-beta2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.elements.arc = {
- backgroundColor: Chart.defaults.global.defaultColor,
- borderColor: "#fff",
- borderWidth: 2
- };
- Chart.elements.Arc = Chart.Element.extend({
- inLabelRange: function (mouseX) {
- var vm = this._view;
- if (vm) {
- return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
- } else {
- return false;
- }
- },
- inRange: function (chartX, chartY) {
- var vm = this._view;
- if (vm) {
- var pointRelativePosition = helpers.getAngleFromPoint(vm, {
- x: chartX,
- y: chartY
- });
- // Put into the range of (-PI/2, 3PI/2]
- var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle;
- var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle;
- //Check if within the range of the open/close angle
- var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
- withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
- return (betweenAngles && withinRadius);
- } else {
- return false;
- }
- },
- tooltipPosition: function () {
- var vm = this._view;
- var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
- rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
- return {
- x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
- y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
- };
- },
- draw: function () {
- var ctx = this._chart.ctx;
- var vm = this._view;
- ctx.beginPath();
- ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
- ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
- ctx.closePath();
- ctx.strokeStyle = vm.borderColor;
- ctx.lineWidth = vm.borderWidth;
- ctx.fillStyle = vm.backgroundColor;
- ctx.fill();
- ctx.lineJoin = 'bevel';
- if (vm.borderWidth) {
- ctx.stroke();
- }
- }
- });
- }).call(this);
- /*!
- * Chart.js
- * http://chartjs.org/
- * Version: 2.0.0-beta2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.elements.line = {
- tension: 0.4,
- backgroundColor: Chart.defaults.global.defaultColor,
- borderWidth: 3,
- borderColor: Chart.defaults.global.defaultColor,
- borderCapStyle: 'butt',
- borderDash: [],
- borderDashOffset: 0.0,
- borderJoinStyle: 'miter',
- fill: true, // do we fill in the area between the line and its base axis
- };
- Chart.elements.Line = Chart.Element.extend({
- lineToNextPoint: function (previousPoint, point, nextPoint, skipHandler, previousSkipHandler) {
- var ctx = this._chart.ctx;
- if (point._view.skip) {
- skipHandler.call(this, previousPoint, point, nextPoint);
- } else if (previousPoint._view.skip) {
- previousSkipHandler.call(this, previousPoint, point, nextPoint);
- } else if (point._view.tension === 0) {
- ctx.lineTo(point._view.x, point._view.y);
- } else {
- // Line between points
- ctx.bezierCurveTo(
- previousPoint._view.controlPointNextX,
- previousPoint._view.controlPointNextY,
- point._view.controlPointPreviousX,
- point._view.controlPointPreviousY,
- point._view.x,
- point._view.y
- );
- }
- },
- draw: function () {
- var _this = this;
- var vm = this._view;
- var ctx = this._chart.ctx;
- var first = this._children[0];
- var last = this._children[this._children.length - 1];
- function loopBackToStart(drawLineToCenter) {
- if (!first._view.skip && !last._view.skip) {
- // Draw a bezier line from last to first
- ctx.bezierCurveTo(
- last._view.controlPointNextX,
- last._view.controlPointNextY,
- first._view.controlPointPreviousX,
- first._view.controlPointPreviousY,
- first._view.x,
- first._view.y
- );
- } else if (drawLineToCenter) {
- // Go to center
- ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y);
- }
- }
- ctx.save();
- // If we had points and want to fill this line, do so.
- if (this._children.length > 0 && vm.fill) {
- // Draw the background first (so the border is always on top)
- ctx.beginPath();
- helpers.each(this._children, function (point, index) {
- var previous = helpers.previousItem(this._children, index);
- var next = helpers.nextItem(this._children, index);
- // First point moves to it's starting position no matter what
- if (index === 0) {
- if (this._loop) {
- ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y);
- } else {
- ctx.moveTo(point._view.x, vm.scaleZero);
- }
- if (point._view.skip) {
- if (!this._loop) {
- ctx.moveTo(next._view.x, this._view.scaleZero);
- }
- } else {
- ctx.lineTo(point._view.x, point._view.y);
- }
- } else {
- this.lineToNextPoint(previous, point, next, function (previousPoint, point, nextPoint) {
- if (this._loop) {
- // Go to center
- ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y);
- } else {
- ctx.lineTo(previousPoint._view.x, this._view.scaleZero);
- ctx.moveTo(nextPoint._view.x, this._view.scaleZero);
- }
- }, function (previousPoint, point) {
- // If we skipped the last point, draw a line to ourselves so that the fill is nice
- ctx.lineTo(point._view.x, point._view.y);
- });
- }
- }, this);
- // For radial scales, loop back around to the first point
- if (this._loop) {
- loopBackToStart(true);
- } else {
- //Round off the line by going to the base of the chart, back to the start, then fill.
- ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
- ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
- }
- ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
- ctx.closePath();
- ctx.fill();
- }
- // Now draw the line between all the points with any borders
- ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle;
- // IE 9 and 10 do not support line dash
- if (ctx.setLineDash) {
- ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash);
- }
- ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset;
- ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle;
- ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth;
- ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
- ctx.beginPath();
- helpers.each(this._children, function (point, index) {
- var previous = helpers.previousItem(this._children, index);
- var next = helpers.nextItem(this._children, index);
- if (index === 0) {
- ctx.moveTo(point._view.x, point._view.y);
- } else {
- this.lineToNextPoint(previous, point, next, function (previousPoint, point, nextPoint) {
- ctx.moveTo(nextPoint._view.x, nextPoint._view.y);
- }, function (previousPoint, point) {
- // If we skipped the last point, move up to our point preventing a line from being drawn
- ctx.moveTo(point._view.x, point._view.y);
- });
- }
- }, this);
- if (this._loop && this._children.length > 0) {
- loopBackToStart();
- }
- ctx.stroke();
- ctx.restore();
- },
- });
- }).call(this);
- /*!
- * Chart.js
- * http://chartjs.org/
- * Version: 2.0.0-beta2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.elements.point = {
- radius: 3,
- backgroundColor: Chart.defaults.global.defaultColor,
- borderWidth: 1,
- borderColor: Chart.defaults.global.defaultColor,
- // Hover
- hitRadius: 1,
- hoverRadius: 4,
- hoverBorderWidth: 1,
- };
- Chart.elements.Point = Chart.Element.extend({
- inRange: function (mouseX, mouseY) {
- var vm = this._view;
- if (vm) {
- var hoverRange = vm.hitRadius + vm.radius;
- return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
- } else {
- return false;
- }
- },
- inLabelRange: function (mouseX) {
- var vm = this._view;
- if (vm) {
- return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
- } else {
- return false;
- }
- },
- tooltipPosition: function () {
- var vm = this._view;
- return {
- x: vm.x,
- y: vm.y,
- padding: vm.radius + vm.borderWidth
- };
- },
- draw: function () {
- var vm = this._view;
- var ctx = this._chart.ctx;
- if (vm.skip) {
- return;
- }
- if (vm.radius > 0 || vm.borderWidth > 0) {
- ctx.beginPath();
- ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2);
- ctx.closePath();
- ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
- ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
- ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
- ctx.fill();
- ctx.stroke();
- }
- }
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
- Chart.defaults.global.elements.rectangle = {
- backgroundColor: Chart.defaults.global.defaultColor,
- borderWidth: 0,
- borderColor: Chart.defaults.global.defaultColor,
- };
- Chart.elements.Rectangle = Chart.Element.extend({
- draw: function () {
- var ctx = this._chart.ctx;
- var vm = this._view;
- var halfWidth = vm.width / 2,
- leftX = vm.x - halfWidth,
- rightX = vm.x + halfWidth,
- top = vm.base - (vm.base - vm.y),
- halfStroke = vm.borderWidth / 2;
- // Canvas doesn't allow us to stroke inside the width so we can
- // adjust the sizes to fit if we're setting a stroke on the line
- if (vm.borderWidth) {
- leftX += halfStroke;
- rightX -= halfStroke;
- top += halfStroke;
- }
- ctx.beginPath();
- ctx.fillStyle = vm.backgroundColor;
- ctx.strokeStyle = vm.borderColor;
- ctx.lineWidth = vm.borderWidth;
- // It'd be nice to keep this class totally generic to any rectangle
- // and simply specify which border to miss out.
- ctx.moveTo(leftX, vm.base);
- ctx.lineTo(leftX, top);
- ctx.lineTo(rightX, top);
- ctx.lineTo(rightX, vm.base);
- ctx.fill();
- if (vm.borderWidth) {
- ctx.stroke();
- }
- },
- height: function () {
- var vm = this._view;
- return vm.base - vm.y;
- },
- inRange: function (mouseX, mouseY) {
- var vm = this._view;
- var inRange = false;
- if (vm) {
- if (vm.y < vm.base) {
- inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base);
- } else {
- inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y);
- }
- }
- return inRange;
- },
- inLabelRange: function (mouseX) {
- var vm = this._view;
- if (vm) {
- return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
- } else {
- return false;
- }
- },
- tooltipPosition: function () {
- var vm = this._view;
- return {
- x: vm.x,
- y: vm.y
- };
- },
- });
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- Chart.Bar = function (context, config) {
- config.type = 'bar';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- var defaultConfig = {
- hover: {
- mode: 'single',
- },
- scales: {
- xAxes: [{
- type: "linear", // bubble should probably use a linear scale by default
- position: "bottom",
- id: "x-axis-0", // need an ID so datasets can reference the scale
- }],
- yAxes: [{
- type: "linear",
- position: "left",
- id: "y-axis-0",
- }],
- },
- tooltips: {
- callbacks: {
- title: function (tooltipItems, data) {
- // Title doesn't make sense for scatter since we format the data as a point
- return '';
- },
- label: function (tooltipItem, data) {
- return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
- }
- }
- },
- };
- // Register the default config for this type
- Chart.defaults.bubble = defaultConfig;
- Chart.Bubble = function (context, config) {
- config.type = 'bubble';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- Chart.Doughnut = function (context, config) {
- config.type = 'doughnut';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- Chart.Line = function (context, config) {
- config.type = 'line';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- Chart.PolarArea = function (context, config) {
- config.type = 'polarArea';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- var defaultConfig = {
- aspectRatio: 1,
- };
- Chart.Radar = function (context, config) {
- config.options = helpers.configMerge(defaultConfig, config.options);
- config.type = 'radar';
- return new Chart(context, config);
- };
- }).call(this);
- (function () {
- "use strict";
- var root = this;
- var Chart = root.Chart;
- var helpers = Chart.helpers;
- var defaultConfig = {
- hover: {
- mode: 'single',
- },
- scales: {
- xAxes: [{
- type: "linear", // scatter should not use a category axis
- position: "bottom",
- id: "x-axis-1", // need an ID so datasets can reference the scale
- }],
- yAxes: [{
- type: "linear",
- position: "left",
- id: "y-axis-1",
- }],
- },
- tooltips: {
- callbacks: {
- title: function (tooltipItems, data) {
- // Title doesn't make sense for scatter since we format the data as a point
- return '';
- },
- label: function (tooltipItem, data) {
- return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
- }
- }
- },
- };
- // Register the default config for this type
- Chart.defaults.scatter = defaultConfig;
- // Scatter charts use line controllers
- Chart.controllers.scatter = Chart.controllers.line;
- Chart.Scatter = function (context, config) {
- config.type = 'scatter';
- return new Chart(context, config);
- };
- }).call(this);
- return this.Chart;
- }));
|