2
0

007-chart.js 399 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269
  1. /*!
  2. * Chart.js v3.7.1
  3. * https://www.chartjs.org
  4. * (c) 2022 Chart.js Contributors
  5. * Released under the MIT License
  6. */
  7. (function (global, factory) {
  8. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  9. typeof define === 'function' && define.amd ? define(factory) :
  10. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
  11. })(this, (function () { 'use strict';
  12. function fontString(pixelSize, fontStyle, fontFamily) {
  13. return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
  14. }
  15. const requestAnimFrame = (function() {
  16. if (typeof window === 'undefined') {
  17. return function(callback) {
  18. return callback();
  19. };
  20. }
  21. return window.requestAnimationFrame;
  22. }());
  23. function throttled(fn, thisArg, updateFn) {
  24. const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args));
  25. let ticking = false;
  26. let args = [];
  27. return function(...rest) {
  28. args = updateArgs(rest);
  29. if (!ticking) {
  30. ticking = true;
  31. requestAnimFrame.call(window, () => {
  32. ticking = false;
  33. fn.apply(thisArg, args);
  34. });
  35. }
  36. };
  37. }
  38. function debounce(fn, delay) {
  39. let timeout;
  40. return function(...args) {
  41. if (delay) {
  42. clearTimeout(timeout);
  43. timeout = setTimeout(fn, delay, args);
  44. } else {
  45. fn.apply(this, args);
  46. }
  47. return delay;
  48. };
  49. }
  50. const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
  51. const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
  52. const _textX = (align, left, right, rtl) => {
  53. const check = rtl ? 'left' : 'right';
  54. return align === check ? right : align === 'center' ? (left + right) / 2 : left;
  55. };
  56. class Animator {
  57. constructor() {
  58. this._request = null;
  59. this._charts = new Map();
  60. this._running = false;
  61. this._lastDate = undefined;
  62. }
  63. _notify(chart, anims, date, type) {
  64. const callbacks = anims.listeners[type];
  65. const numSteps = anims.duration;
  66. callbacks.forEach(fn => fn({
  67. chart,
  68. initial: anims.initial,
  69. numSteps,
  70. currentStep: Math.min(date - anims.start, numSteps)
  71. }));
  72. }
  73. _refresh() {
  74. if (this._request) {
  75. return;
  76. }
  77. this._running = true;
  78. this._request = requestAnimFrame.call(window, () => {
  79. this._update();
  80. this._request = null;
  81. if (this._running) {
  82. this._refresh();
  83. }
  84. });
  85. }
  86. _update(date = Date.now()) {
  87. let remaining = 0;
  88. this._charts.forEach((anims, chart) => {
  89. if (!anims.running || !anims.items.length) {
  90. return;
  91. }
  92. const items = anims.items;
  93. let i = items.length - 1;
  94. let draw = false;
  95. let item;
  96. for (; i >= 0; --i) {
  97. item = items[i];
  98. if (item._active) {
  99. if (item._total > anims.duration) {
  100. anims.duration = item._total;
  101. }
  102. item.tick(date);
  103. draw = true;
  104. } else {
  105. items[i] = items[items.length - 1];
  106. items.pop();
  107. }
  108. }
  109. if (draw) {
  110. chart.draw();
  111. this._notify(chart, anims, date, 'progress');
  112. }
  113. if (!items.length) {
  114. anims.running = false;
  115. this._notify(chart, anims, date, 'complete');
  116. anims.initial = false;
  117. }
  118. remaining += items.length;
  119. });
  120. this._lastDate = date;
  121. if (remaining === 0) {
  122. this._running = false;
  123. }
  124. }
  125. _getAnims(chart) {
  126. const charts = this._charts;
  127. let anims = charts.get(chart);
  128. if (!anims) {
  129. anims = {
  130. running: false,
  131. initial: true,
  132. items: [],
  133. listeners: {
  134. complete: [],
  135. progress: []
  136. }
  137. };
  138. charts.set(chart, anims);
  139. }
  140. return anims;
  141. }
  142. listen(chart, event, cb) {
  143. this._getAnims(chart).listeners[event].push(cb);
  144. }
  145. add(chart, items) {
  146. if (!items || !items.length) {
  147. return;
  148. }
  149. this._getAnims(chart).items.push(...items);
  150. }
  151. has(chart) {
  152. return this._getAnims(chart).items.length > 0;
  153. }
  154. start(chart) {
  155. const anims = this._charts.get(chart);
  156. if (!anims) {
  157. return;
  158. }
  159. anims.running = true;
  160. anims.start = Date.now();
  161. anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);
  162. this._refresh();
  163. }
  164. running(chart) {
  165. if (!this._running) {
  166. return false;
  167. }
  168. const anims = this._charts.get(chart);
  169. if (!anims || !anims.running || !anims.items.length) {
  170. return false;
  171. }
  172. return true;
  173. }
  174. stop(chart) {
  175. const anims = this._charts.get(chart);
  176. if (!anims || !anims.items.length) {
  177. return;
  178. }
  179. const items = anims.items;
  180. let i = items.length - 1;
  181. for (; i >= 0; --i) {
  182. items[i].cancel();
  183. }
  184. anims.items = [];
  185. this._notify(chart, anims, Date.now(), 'complete');
  186. }
  187. remove(chart) {
  188. return this._charts.delete(chart);
  189. }
  190. }
  191. var animator = new Animator();
  192. /*!
  193. * @kurkle/color v0.1.9
  194. * https://github.com/kurkle/color#readme
  195. * (c) 2020 Jukka Kurkela
  196. * Released under the MIT License
  197. */
  198. const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15};
  199. const hex = '0123456789ABCDEF';
  200. const h1 = (b) => hex[b & 0xF];
  201. const h2 = (b) => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
  202. const eq = (b) => (((b & 0xF0) >> 4) === (b & 0xF));
  203. function isShort(v) {
  204. return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
  205. }
  206. function hexParse(str) {
  207. var len = str.length;
  208. var ret;
  209. if (str[0] === '#') {
  210. if (len === 4 || len === 5) {
  211. ret = {
  212. r: 255 & map$1[str[1]] * 17,
  213. g: 255 & map$1[str[2]] * 17,
  214. b: 255 & map$1[str[3]] * 17,
  215. a: len === 5 ? map$1[str[4]] * 17 : 255
  216. };
  217. } else if (len === 7 || len === 9) {
  218. ret = {
  219. r: map$1[str[1]] << 4 | map$1[str[2]],
  220. g: map$1[str[3]] << 4 | map$1[str[4]],
  221. b: map$1[str[5]] << 4 | map$1[str[6]],
  222. a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
  223. };
  224. }
  225. }
  226. return ret;
  227. }
  228. function hexString(v) {
  229. var f = isShort(v) ? h1 : h2;
  230. return v
  231. ? '#' + f(v.r) + f(v.g) + f(v.b) + (v.a < 255 ? f(v.a) : '')
  232. : v;
  233. }
  234. function round(v) {
  235. return v + 0.5 | 0;
  236. }
  237. const lim = (v, l, h) => Math.max(Math.min(v, h), l);
  238. function p2b(v) {
  239. return lim(round(v * 2.55), 0, 255);
  240. }
  241. function n2b(v) {
  242. return lim(round(v * 255), 0, 255);
  243. }
  244. function b2n(v) {
  245. return lim(round(v / 2.55) / 100, 0, 1);
  246. }
  247. function n2p(v) {
  248. return lim(round(v * 100), 0, 100);
  249. }
  250. const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
  251. function rgbParse(str) {
  252. const m = RGB_RE.exec(str);
  253. let a = 255;
  254. let r, g, b;
  255. if (!m) {
  256. return;
  257. }
  258. if (m[7] !== r) {
  259. const v = +m[7];
  260. a = 255 & (m[8] ? p2b(v) : v * 255);
  261. }
  262. r = +m[1];
  263. g = +m[3];
  264. b = +m[5];
  265. r = 255 & (m[2] ? p2b(r) : r);
  266. g = 255 & (m[4] ? p2b(g) : g);
  267. b = 255 & (m[6] ? p2b(b) : b);
  268. return {
  269. r: r,
  270. g: g,
  271. b: b,
  272. a: a
  273. };
  274. }
  275. function rgbString(v) {
  276. return v && (
  277. v.a < 255
  278. ? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
  279. : `rgb(${v.r}, ${v.g}, ${v.b})`
  280. );
  281. }
  282. const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
  283. function hsl2rgbn(h, s, l) {
  284. const a = s * Math.min(l, 1 - l);
  285. const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  286. return [f(0), f(8), f(4)];
  287. }
  288. function hsv2rgbn(h, s, v) {
  289. const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
  290. return [f(5), f(3), f(1)];
  291. }
  292. function hwb2rgbn(h, w, b) {
  293. const rgb = hsl2rgbn(h, 1, 0.5);
  294. let i;
  295. if (w + b > 1) {
  296. i = 1 / (w + b);
  297. w *= i;
  298. b *= i;
  299. }
  300. for (i = 0; i < 3; i++) {
  301. rgb[i] *= 1 - w - b;
  302. rgb[i] += w;
  303. }
  304. return rgb;
  305. }
  306. function rgb2hsl(v) {
  307. const range = 255;
  308. const r = v.r / range;
  309. const g = v.g / range;
  310. const b = v.b / range;
  311. const max = Math.max(r, g, b);
  312. const min = Math.min(r, g, b);
  313. const l = (max + min) / 2;
  314. let h, s, d;
  315. if (max !== min) {
  316. d = max - min;
  317. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  318. h = max === r
  319. ? ((g - b) / d) + (g < b ? 6 : 0)
  320. : max === g
  321. ? (b - r) / d + 2
  322. : (r - g) / d + 4;
  323. h = h * 60 + 0.5;
  324. }
  325. return [h | 0, s || 0, l];
  326. }
  327. function calln(f, a, b, c) {
  328. return (
  329. Array.isArray(a)
  330. ? f(a[0], a[1], a[2])
  331. : f(a, b, c)
  332. ).map(n2b);
  333. }
  334. function hsl2rgb(h, s, l) {
  335. return calln(hsl2rgbn, h, s, l);
  336. }
  337. function hwb2rgb(h, w, b) {
  338. return calln(hwb2rgbn, h, w, b);
  339. }
  340. function hsv2rgb(h, s, v) {
  341. return calln(hsv2rgbn, h, s, v);
  342. }
  343. function hue(h) {
  344. return (h % 360 + 360) % 360;
  345. }
  346. function hueParse(str) {
  347. const m = HUE_RE.exec(str);
  348. let a = 255;
  349. let v;
  350. if (!m) {
  351. return;
  352. }
  353. if (m[5] !== v) {
  354. a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
  355. }
  356. const h = hue(+m[2]);
  357. const p1 = +m[3] / 100;
  358. const p2 = +m[4] / 100;
  359. if (m[1] === 'hwb') {
  360. v = hwb2rgb(h, p1, p2);
  361. } else if (m[1] === 'hsv') {
  362. v = hsv2rgb(h, p1, p2);
  363. } else {
  364. v = hsl2rgb(h, p1, p2);
  365. }
  366. return {
  367. r: v[0],
  368. g: v[1],
  369. b: v[2],
  370. a: a
  371. };
  372. }
  373. function rotate(v, deg) {
  374. var h = rgb2hsl(v);
  375. h[0] = hue(h[0] + deg);
  376. h = hsl2rgb(h);
  377. v.r = h[0];
  378. v.g = h[1];
  379. v.b = h[2];
  380. }
  381. function hslString(v) {
  382. if (!v) {
  383. return;
  384. }
  385. const a = rgb2hsl(v);
  386. const h = a[0];
  387. const s = n2p(a[1]);
  388. const l = n2p(a[2]);
  389. return v.a < 255
  390. ? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
  391. : `hsl(${h}, ${s}%, ${l}%)`;
  392. }
  393. const map$1$1 = {
  394. x: 'dark',
  395. Z: 'light',
  396. Y: 're',
  397. X: 'blu',
  398. W: 'gr',
  399. V: 'medium',
  400. U: 'slate',
  401. A: 'ee',
  402. T: 'ol',
  403. S: 'or',
  404. B: 'ra',
  405. C: 'lateg',
  406. D: 'ights',
  407. R: 'in',
  408. Q: 'turquois',
  409. E: 'hi',
  410. P: 'ro',
  411. O: 'al',
  412. N: 'le',
  413. M: 'de',
  414. L: 'yello',
  415. F: 'en',
  416. K: 'ch',
  417. G: 'arks',
  418. H: 'ea',
  419. I: 'ightg',
  420. J: 'wh'
  421. };
  422. const names = {
  423. OiceXe: 'f0f8ff',
  424. antiquewEte: 'faebd7',
  425. aqua: 'ffff',
  426. aquamarRe: '7fffd4',
  427. azuY: 'f0ffff',
  428. beige: 'f5f5dc',
  429. bisque: 'ffe4c4',
  430. black: '0',
  431. blanKedOmond: 'ffebcd',
  432. Xe: 'ff',
  433. XeviTet: '8a2be2',
  434. bPwn: 'a52a2a',
  435. burlywood: 'deb887',
  436. caMtXe: '5f9ea0',
  437. KartYuse: '7fff00',
  438. KocTate: 'd2691e',
  439. cSO: 'ff7f50',
  440. cSnflowerXe: '6495ed',
  441. cSnsilk: 'fff8dc',
  442. crimson: 'dc143c',
  443. cyan: 'ffff',
  444. xXe: '8b',
  445. xcyan: '8b8b',
  446. xgTMnPd: 'b8860b',
  447. xWay: 'a9a9a9',
  448. xgYF: '6400',
  449. xgYy: 'a9a9a9',
  450. xkhaki: 'bdb76b',
  451. xmagFta: '8b008b',
  452. xTivegYF: '556b2f',
  453. xSange: 'ff8c00',
  454. xScEd: '9932cc',
  455. xYd: '8b0000',
  456. xsOmon: 'e9967a',
  457. xsHgYF: '8fbc8f',
  458. xUXe: '483d8b',
  459. xUWay: '2f4f4f',
  460. xUgYy: '2f4f4f',
  461. xQe: 'ced1',
  462. xviTet: '9400d3',
  463. dAppRk: 'ff1493',
  464. dApskyXe: 'bfff',
  465. dimWay: '696969',
  466. dimgYy: '696969',
  467. dodgerXe: '1e90ff',
  468. fiYbrick: 'b22222',
  469. flSOwEte: 'fffaf0',
  470. foYstWAn: '228b22',
  471. fuKsia: 'ff00ff',
  472. gaRsbSo: 'dcdcdc',
  473. ghostwEte: 'f8f8ff',
  474. gTd: 'ffd700',
  475. gTMnPd: 'daa520',
  476. Way: '808080',
  477. gYF: '8000',
  478. gYFLw: 'adff2f',
  479. gYy: '808080',
  480. honeyMw: 'f0fff0',
  481. hotpRk: 'ff69b4',
  482. RdianYd: 'cd5c5c',
  483. Rdigo: '4b0082',
  484. ivSy: 'fffff0',
  485. khaki: 'f0e68c',
  486. lavFMr: 'e6e6fa',
  487. lavFMrXsh: 'fff0f5',
  488. lawngYF: '7cfc00',
  489. NmoncEffon: 'fffacd',
  490. ZXe: 'add8e6',
  491. ZcSO: 'f08080',
  492. Zcyan: 'e0ffff',
  493. ZgTMnPdLw: 'fafad2',
  494. ZWay: 'd3d3d3',
  495. ZgYF: '90ee90',
  496. ZgYy: 'd3d3d3',
  497. ZpRk: 'ffb6c1',
  498. ZsOmon: 'ffa07a',
  499. ZsHgYF: '20b2aa',
  500. ZskyXe: '87cefa',
  501. ZUWay: '778899',
  502. ZUgYy: '778899',
  503. ZstAlXe: 'b0c4de',
  504. ZLw: 'ffffe0',
  505. lime: 'ff00',
  506. limegYF: '32cd32',
  507. lRF: 'faf0e6',
  508. magFta: 'ff00ff',
  509. maPon: '800000',
  510. VaquamarRe: '66cdaa',
  511. VXe: 'cd',
  512. VScEd: 'ba55d3',
  513. VpurpN: '9370db',
  514. VsHgYF: '3cb371',
  515. VUXe: '7b68ee',
  516. VsprRggYF: 'fa9a',
  517. VQe: '48d1cc',
  518. VviTetYd: 'c71585',
  519. midnightXe: '191970',
  520. mRtcYam: 'f5fffa',
  521. mistyPse: 'ffe4e1',
  522. moccasR: 'ffe4b5',
  523. navajowEte: 'ffdead',
  524. navy: '80',
  525. Tdlace: 'fdf5e6',
  526. Tive: '808000',
  527. TivedBb: '6b8e23',
  528. Sange: 'ffa500',
  529. SangeYd: 'ff4500',
  530. ScEd: 'da70d6',
  531. pOegTMnPd: 'eee8aa',
  532. pOegYF: '98fb98',
  533. pOeQe: 'afeeee',
  534. pOeviTetYd: 'db7093',
  535. papayawEp: 'ffefd5',
  536. pHKpuff: 'ffdab9',
  537. peru: 'cd853f',
  538. pRk: 'ffc0cb',
  539. plum: 'dda0dd',
  540. powMrXe: 'b0e0e6',
  541. purpN: '800080',
  542. YbeccapurpN: '663399',
  543. Yd: 'ff0000',
  544. Psybrown: 'bc8f8f',
  545. PyOXe: '4169e1',
  546. saddNbPwn: '8b4513',
  547. sOmon: 'fa8072',
  548. sandybPwn: 'f4a460',
  549. sHgYF: '2e8b57',
  550. sHshell: 'fff5ee',
  551. siFna: 'a0522d',
  552. silver: 'c0c0c0',
  553. skyXe: '87ceeb',
  554. UXe: '6a5acd',
  555. UWay: '708090',
  556. UgYy: '708090',
  557. snow: 'fffafa',
  558. sprRggYF: 'ff7f',
  559. stAlXe: '4682b4',
  560. tan: 'd2b48c',
  561. teO: '8080',
  562. tEstN: 'd8bfd8',
  563. tomato: 'ff6347',
  564. Qe: '40e0d0',
  565. viTet: 'ee82ee',
  566. JHt: 'f5deb3',
  567. wEte: 'ffffff',
  568. wEtesmoke: 'f5f5f5',
  569. Lw: 'ffff00',
  570. LwgYF: '9acd32'
  571. };
  572. function unpack() {
  573. const unpacked = {};
  574. const keys = Object.keys(names);
  575. const tkeys = Object.keys(map$1$1);
  576. let i, j, k, ok, nk;
  577. for (i = 0; i < keys.length; i++) {
  578. ok = nk = keys[i];
  579. for (j = 0; j < tkeys.length; j++) {
  580. k = tkeys[j];
  581. nk = nk.replace(k, map$1$1[k]);
  582. }
  583. k = parseInt(names[ok], 16);
  584. unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
  585. }
  586. return unpacked;
  587. }
  588. let names$1;
  589. function nameParse(str) {
  590. if (!names$1) {
  591. names$1 = unpack();
  592. names$1.transparent = [0, 0, 0, 0];
  593. }
  594. const a = names$1[str.toLowerCase()];
  595. return a && {
  596. r: a[0],
  597. g: a[1],
  598. b: a[2],
  599. a: a.length === 4 ? a[3] : 255
  600. };
  601. }
  602. function modHSL(v, i, ratio) {
  603. if (v) {
  604. let tmp = rgb2hsl(v);
  605. tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
  606. tmp = hsl2rgb(tmp);
  607. v.r = tmp[0];
  608. v.g = tmp[1];
  609. v.b = tmp[2];
  610. }
  611. }
  612. function clone$1(v, proto) {
  613. return v ? Object.assign(proto || {}, v) : v;
  614. }
  615. function fromObject(input) {
  616. var v = {r: 0, g: 0, b: 0, a: 255};
  617. if (Array.isArray(input)) {
  618. if (input.length >= 3) {
  619. v = {r: input[0], g: input[1], b: input[2], a: 255};
  620. if (input.length > 3) {
  621. v.a = n2b(input[3]);
  622. }
  623. }
  624. } else {
  625. v = clone$1(input, {r: 0, g: 0, b: 0, a: 1});
  626. v.a = n2b(v.a);
  627. }
  628. return v;
  629. }
  630. function functionParse(str) {
  631. if (str.charAt(0) === 'r') {
  632. return rgbParse(str);
  633. }
  634. return hueParse(str);
  635. }
  636. class Color {
  637. constructor(input) {
  638. if (input instanceof Color) {
  639. return input;
  640. }
  641. const type = typeof input;
  642. let v;
  643. if (type === 'object') {
  644. v = fromObject(input);
  645. } else if (type === 'string') {
  646. v = hexParse(input) || nameParse(input) || functionParse(input);
  647. }
  648. this._rgb = v;
  649. this._valid = !!v;
  650. }
  651. get valid() {
  652. return this._valid;
  653. }
  654. get rgb() {
  655. var v = clone$1(this._rgb);
  656. if (v) {
  657. v.a = b2n(v.a);
  658. }
  659. return v;
  660. }
  661. set rgb(obj) {
  662. this._rgb = fromObject(obj);
  663. }
  664. rgbString() {
  665. return this._valid ? rgbString(this._rgb) : this._rgb;
  666. }
  667. hexString() {
  668. return this._valid ? hexString(this._rgb) : this._rgb;
  669. }
  670. hslString() {
  671. return this._valid ? hslString(this._rgb) : this._rgb;
  672. }
  673. mix(color, weight) {
  674. const me = this;
  675. if (color) {
  676. const c1 = me.rgb;
  677. const c2 = color.rgb;
  678. let w2;
  679. const p = weight === w2 ? 0.5 : weight;
  680. const w = 2 * p - 1;
  681. const a = c1.a - c2.a;
  682. const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
  683. w2 = 1 - w1;
  684. c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
  685. c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
  686. c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
  687. c1.a = p * c1.a + (1 - p) * c2.a;
  688. me.rgb = c1;
  689. }
  690. return me;
  691. }
  692. clone() {
  693. return new Color(this.rgb);
  694. }
  695. alpha(a) {
  696. this._rgb.a = n2b(a);
  697. return this;
  698. }
  699. clearer(ratio) {
  700. const rgb = this._rgb;
  701. rgb.a *= 1 - ratio;
  702. return this;
  703. }
  704. greyscale() {
  705. const rgb = this._rgb;
  706. const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
  707. rgb.r = rgb.g = rgb.b = val;
  708. return this;
  709. }
  710. opaquer(ratio) {
  711. const rgb = this._rgb;
  712. rgb.a *= 1 + ratio;
  713. return this;
  714. }
  715. negate() {
  716. const v = this._rgb;
  717. v.r = 255 - v.r;
  718. v.g = 255 - v.g;
  719. v.b = 255 - v.b;
  720. return this;
  721. }
  722. lighten(ratio) {
  723. modHSL(this._rgb, 2, ratio);
  724. return this;
  725. }
  726. darken(ratio) {
  727. modHSL(this._rgb, 2, -ratio);
  728. return this;
  729. }
  730. saturate(ratio) {
  731. modHSL(this._rgb, 1, ratio);
  732. return this;
  733. }
  734. desaturate(ratio) {
  735. modHSL(this._rgb, 1, -ratio);
  736. return this;
  737. }
  738. rotate(deg) {
  739. rotate(this._rgb, deg);
  740. return this;
  741. }
  742. }
  743. function index_esm(input) {
  744. return new Color(input);
  745. }
  746. const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern;
  747. function color(value) {
  748. return isPatternOrGradient(value) ? value : index_esm(value);
  749. }
  750. function getHoverColor(value) {
  751. return isPatternOrGradient(value)
  752. ? value
  753. : index_esm(value).saturate(0.5).darken(0.1).hexString();
  754. }
  755. function noop() {}
  756. const uid = (function() {
  757. let id = 0;
  758. return function() {
  759. return id++;
  760. };
  761. }());
  762. function isNullOrUndef(value) {
  763. return value === null || typeof value === 'undefined';
  764. }
  765. function isArray(value) {
  766. if (Array.isArray && Array.isArray(value)) {
  767. return true;
  768. }
  769. const type = Object.prototype.toString.call(value);
  770. if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
  771. return true;
  772. }
  773. return false;
  774. }
  775. function isObject(value) {
  776. return value !== null && Object.prototype.toString.call(value) === '[object Object]';
  777. }
  778. const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value);
  779. function finiteOrDefault(value, defaultValue) {
  780. return isNumberFinite(value) ? value : defaultValue;
  781. }
  782. function valueOrDefault(value, defaultValue) {
  783. return typeof value === 'undefined' ? defaultValue : value;
  784. }
  785. const toPercentage = (value, dimension) =>
  786. typeof value === 'string' && value.endsWith('%') ?
  787. parseFloat(value) / 100
  788. : value / dimension;
  789. const toDimension = (value, dimension) =>
  790. typeof value === 'string' && value.endsWith('%') ?
  791. parseFloat(value) / 100 * dimension
  792. : +value;
  793. function callback(fn, args, thisArg) {
  794. if (fn && typeof fn.call === 'function') {
  795. return fn.apply(thisArg, args);
  796. }
  797. }
  798. function each(loopable, fn, thisArg, reverse) {
  799. let i, len, keys;
  800. if (isArray(loopable)) {
  801. len = loopable.length;
  802. if (reverse) {
  803. for (i = len - 1; i >= 0; i--) {
  804. fn.call(thisArg, loopable[i], i);
  805. }
  806. } else {
  807. for (i = 0; i < len; i++) {
  808. fn.call(thisArg, loopable[i], i);
  809. }
  810. }
  811. } else if (isObject(loopable)) {
  812. keys = Object.keys(loopable);
  813. len = keys.length;
  814. for (i = 0; i < len; i++) {
  815. fn.call(thisArg, loopable[keys[i]], keys[i]);
  816. }
  817. }
  818. }
  819. function _elementsEqual(a0, a1) {
  820. let i, ilen, v0, v1;
  821. if (!a0 || !a1 || a0.length !== a1.length) {
  822. return false;
  823. }
  824. for (i = 0, ilen = a0.length; i < ilen; ++i) {
  825. v0 = a0[i];
  826. v1 = a1[i];
  827. if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
  828. return false;
  829. }
  830. }
  831. return true;
  832. }
  833. function clone(source) {
  834. if (isArray(source)) {
  835. return source.map(clone);
  836. }
  837. if (isObject(source)) {
  838. const target = Object.create(null);
  839. const keys = Object.keys(source);
  840. const klen = keys.length;
  841. let k = 0;
  842. for (; k < klen; ++k) {
  843. target[keys[k]] = clone(source[keys[k]]);
  844. }
  845. return target;
  846. }
  847. return source;
  848. }
  849. function isValidKey(key) {
  850. return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;
  851. }
  852. function _merger(key, target, source, options) {
  853. if (!isValidKey(key)) {
  854. return;
  855. }
  856. const tval = target[key];
  857. const sval = source[key];
  858. if (isObject(tval) && isObject(sval)) {
  859. merge(tval, sval, options);
  860. } else {
  861. target[key] = clone(sval);
  862. }
  863. }
  864. function merge(target, source, options) {
  865. const sources = isArray(source) ? source : [source];
  866. const ilen = sources.length;
  867. if (!isObject(target)) {
  868. return target;
  869. }
  870. options = options || {};
  871. const merger = options.merger || _merger;
  872. for (let i = 0; i < ilen; ++i) {
  873. source = sources[i];
  874. if (!isObject(source)) {
  875. continue;
  876. }
  877. const keys = Object.keys(source);
  878. for (let k = 0, klen = keys.length; k < klen; ++k) {
  879. merger(keys[k], target, source, options);
  880. }
  881. }
  882. return target;
  883. }
  884. function mergeIf(target, source) {
  885. return merge(target, source, {merger: _mergerIf});
  886. }
  887. function _mergerIf(key, target, source) {
  888. if (!isValidKey(key)) {
  889. return;
  890. }
  891. const tval = target[key];
  892. const sval = source[key];
  893. if (isObject(tval) && isObject(sval)) {
  894. mergeIf(tval, sval);
  895. } else if (!Object.prototype.hasOwnProperty.call(target, key)) {
  896. target[key] = clone(sval);
  897. }
  898. }
  899. function _deprecated(scope, value, previous, current) {
  900. if (value !== undefined) {
  901. console.warn(scope + ': "' + previous +
  902. '" is deprecated. Please use "' + current + '" instead');
  903. }
  904. }
  905. const emptyString = '';
  906. const dot = '.';
  907. function indexOfDotOrLength(key, start) {
  908. const idx = key.indexOf(dot, start);
  909. return idx === -1 ? key.length : idx;
  910. }
  911. function resolveObjectKey(obj, key) {
  912. if (key === emptyString) {
  913. return obj;
  914. }
  915. let pos = 0;
  916. let idx = indexOfDotOrLength(key, pos);
  917. while (obj && idx > pos) {
  918. obj = obj[key.substr(pos, idx - pos)];
  919. pos = idx + 1;
  920. idx = indexOfDotOrLength(key, pos);
  921. }
  922. return obj;
  923. }
  924. function _capitalize(str) {
  925. return str.charAt(0).toUpperCase() + str.slice(1);
  926. }
  927. const defined = (value) => typeof value !== 'undefined';
  928. const isFunction = (value) => typeof value === 'function';
  929. const setsEqual = (a, b) => {
  930. if (a.size !== b.size) {
  931. return false;
  932. }
  933. for (const item of a) {
  934. if (!b.has(item)) {
  935. return false;
  936. }
  937. }
  938. return true;
  939. };
  940. function _isClickEvent(e) {
  941. return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
  942. }
  943. const overrides = Object.create(null);
  944. const descriptors = Object.create(null);
  945. function getScope$1(node, key) {
  946. if (!key) {
  947. return node;
  948. }
  949. const keys = key.split('.');
  950. for (let i = 0, n = keys.length; i < n; ++i) {
  951. const k = keys[i];
  952. node = node[k] || (node[k] = Object.create(null));
  953. }
  954. return node;
  955. }
  956. function set(root, scope, values) {
  957. if (typeof scope === 'string') {
  958. return merge(getScope$1(root, scope), values);
  959. }
  960. return merge(getScope$1(root, ''), scope);
  961. }
  962. class Defaults {
  963. constructor(_descriptors) {
  964. this.animation = undefined;
  965. this.backgroundColor = 'rgba(0,0,0,0.1)';
  966. this.borderColor = 'rgba(0,0,0,0.1)';
  967. this.color = '#666';
  968. this.datasets = {};
  969. this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio();
  970. this.elements = {};
  971. this.events = [
  972. 'mousemove',
  973. 'mouseout',
  974. 'click',
  975. 'touchstart',
  976. 'touchmove'
  977. ];
  978. this.font = {
  979. family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
  980. size: 12,
  981. style: 'normal',
  982. lineHeight: 1.2,
  983. weight: null
  984. };
  985. this.hover = {};
  986. this.hoverBackgroundColor = (ctx, options) => getHoverColor(options.backgroundColor);
  987. this.hoverBorderColor = (ctx, options) => getHoverColor(options.borderColor);
  988. this.hoverColor = (ctx, options) => getHoverColor(options.color);
  989. this.indexAxis = 'x';
  990. this.interaction = {
  991. mode: 'nearest',
  992. intersect: true
  993. };
  994. this.maintainAspectRatio = true;
  995. this.onHover = null;
  996. this.onClick = null;
  997. this.parsing = true;
  998. this.plugins = {};
  999. this.responsive = true;
  1000. this.scale = undefined;
  1001. this.scales = {};
  1002. this.showLine = true;
  1003. this.drawActiveElementsOnTop = true;
  1004. this.describe(_descriptors);
  1005. }
  1006. set(scope, values) {
  1007. return set(this, scope, values);
  1008. }
  1009. get(scope) {
  1010. return getScope$1(this, scope);
  1011. }
  1012. describe(scope, values) {
  1013. return set(descriptors, scope, values);
  1014. }
  1015. override(scope, values) {
  1016. return set(overrides, scope, values);
  1017. }
  1018. route(scope, name, targetScope, targetName) {
  1019. const scopeObject = getScope$1(this, scope);
  1020. const targetScopeObject = getScope$1(this, targetScope);
  1021. const privateName = '_' + name;
  1022. Object.defineProperties(scopeObject, {
  1023. [privateName]: {
  1024. value: scopeObject[name],
  1025. writable: true
  1026. },
  1027. [name]: {
  1028. enumerable: true,
  1029. get() {
  1030. const local = this[privateName];
  1031. const target = targetScopeObject[targetName];
  1032. if (isObject(local)) {
  1033. return Object.assign({}, target, local);
  1034. }
  1035. return valueOrDefault(local, target);
  1036. },
  1037. set(value) {
  1038. this[privateName] = value;
  1039. }
  1040. }
  1041. });
  1042. }
  1043. }
  1044. var defaults = new Defaults({
  1045. _scriptable: (name) => !name.startsWith('on'),
  1046. _indexable: (name) => name !== 'events',
  1047. hover: {
  1048. _fallback: 'interaction'
  1049. },
  1050. interaction: {
  1051. _scriptable: false,
  1052. _indexable: false,
  1053. }
  1054. });
  1055. const PI = Math.PI;
  1056. const TAU = 2 * PI;
  1057. const PITAU = TAU + PI;
  1058. const INFINITY = Number.POSITIVE_INFINITY;
  1059. const RAD_PER_DEG = PI / 180;
  1060. const HALF_PI = PI / 2;
  1061. const QUARTER_PI = PI / 4;
  1062. const TWO_THIRDS_PI = PI * 2 / 3;
  1063. const log10 = Math.log10;
  1064. const sign = Math.sign;
  1065. function niceNum(range) {
  1066. const roundedRange = Math.round(range);
  1067. range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
  1068. const niceRange = Math.pow(10, Math.floor(log10(range)));
  1069. const fraction = range / niceRange;
  1070. const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
  1071. return niceFraction * niceRange;
  1072. }
  1073. function _factorize(value) {
  1074. const result = [];
  1075. const sqrt = Math.sqrt(value);
  1076. let i;
  1077. for (i = 1; i < sqrt; i++) {
  1078. if (value % i === 0) {
  1079. result.push(i);
  1080. result.push(value / i);
  1081. }
  1082. }
  1083. if (sqrt === (sqrt | 0)) {
  1084. result.push(sqrt);
  1085. }
  1086. result.sort((a, b) => a - b).pop();
  1087. return result;
  1088. }
  1089. function isNumber(n) {
  1090. return !isNaN(parseFloat(n)) && isFinite(n);
  1091. }
  1092. function almostEquals(x, y, epsilon) {
  1093. return Math.abs(x - y) < epsilon;
  1094. }
  1095. function almostWhole(x, epsilon) {
  1096. const rounded = Math.round(x);
  1097. return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
  1098. }
  1099. function _setMinAndMaxByKey(array, target, property) {
  1100. let i, ilen, value;
  1101. for (i = 0, ilen = array.length; i < ilen; i++) {
  1102. value = array[i][property];
  1103. if (!isNaN(value)) {
  1104. target.min = Math.min(target.min, value);
  1105. target.max = Math.max(target.max, value);
  1106. }
  1107. }
  1108. }
  1109. function toRadians(degrees) {
  1110. return degrees * (PI / 180);
  1111. }
  1112. function toDegrees(radians) {
  1113. return radians * (180 / PI);
  1114. }
  1115. function _decimalPlaces(x) {
  1116. if (!isNumberFinite(x)) {
  1117. return;
  1118. }
  1119. let e = 1;
  1120. let p = 0;
  1121. while (Math.round(x * e) / e !== x) {
  1122. e *= 10;
  1123. p++;
  1124. }
  1125. return p;
  1126. }
  1127. function getAngleFromPoint(centrePoint, anglePoint) {
  1128. const distanceFromXCenter = anglePoint.x - centrePoint.x;
  1129. const distanceFromYCenter = anglePoint.y - centrePoint.y;
  1130. const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
  1131. let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
  1132. if (angle < (-0.5 * PI)) {
  1133. angle += TAU;
  1134. }
  1135. return {
  1136. angle,
  1137. distance: radialDistanceFromCenter
  1138. };
  1139. }
  1140. function distanceBetweenPoints(pt1, pt2) {
  1141. return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
  1142. }
  1143. function _angleDiff(a, b) {
  1144. return (a - b + PITAU) % TAU - PI;
  1145. }
  1146. function _normalizeAngle(a) {
  1147. return (a % TAU + TAU) % TAU;
  1148. }
  1149. function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
  1150. const a = _normalizeAngle(angle);
  1151. const s = _normalizeAngle(start);
  1152. const e = _normalizeAngle(end);
  1153. const angleToStart = _normalizeAngle(s - a);
  1154. const angleToEnd = _normalizeAngle(e - a);
  1155. const startToAngle = _normalizeAngle(a - s);
  1156. const endToAngle = _normalizeAngle(a - e);
  1157. return a === s || a === e || (sameAngleIsFullCircle && s === e)
  1158. || (angleToStart > angleToEnd && startToAngle < endToAngle);
  1159. }
  1160. function _limitValue(value, min, max) {
  1161. return Math.max(min, Math.min(max, value));
  1162. }
  1163. function _int16Range(value) {
  1164. return _limitValue(value, -32768, 32767);
  1165. }
  1166. function _isBetween(value, start, end, epsilon = 1e-6) {
  1167. return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
  1168. }
  1169. function toFontString(font) {
  1170. if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
  1171. return null;
  1172. }
  1173. return (font.style ? font.style + ' ' : '')
  1174. + (font.weight ? font.weight + ' ' : '')
  1175. + font.size + 'px '
  1176. + font.family;
  1177. }
  1178. function _measureText(ctx, data, gc, longest, string) {
  1179. let textWidth = data[string];
  1180. if (!textWidth) {
  1181. textWidth = data[string] = ctx.measureText(string).width;
  1182. gc.push(string);
  1183. }
  1184. if (textWidth > longest) {
  1185. longest = textWidth;
  1186. }
  1187. return longest;
  1188. }
  1189. function _longestText(ctx, font, arrayOfThings, cache) {
  1190. cache = cache || {};
  1191. let data = cache.data = cache.data || {};
  1192. let gc = cache.garbageCollect = cache.garbageCollect || [];
  1193. if (cache.font !== font) {
  1194. data = cache.data = {};
  1195. gc = cache.garbageCollect = [];
  1196. cache.font = font;
  1197. }
  1198. ctx.save();
  1199. ctx.font = font;
  1200. let longest = 0;
  1201. const ilen = arrayOfThings.length;
  1202. let i, j, jlen, thing, nestedThing;
  1203. for (i = 0; i < ilen; i++) {
  1204. thing = arrayOfThings[i];
  1205. if (thing !== undefined && thing !== null && isArray(thing) !== true) {
  1206. longest = _measureText(ctx, data, gc, longest, thing);
  1207. } else if (isArray(thing)) {
  1208. for (j = 0, jlen = thing.length; j < jlen; j++) {
  1209. nestedThing = thing[j];
  1210. if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
  1211. longest = _measureText(ctx, data, gc, longest, nestedThing);
  1212. }
  1213. }
  1214. }
  1215. }
  1216. ctx.restore();
  1217. const gcLen = gc.length / 2;
  1218. if (gcLen > arrayOfThings.length) {
  1219. for (i = 0; i < gcLen; i++) {
  1220. delete data[gc[i]];
  1221. }
  1222. gc.splice(0, gcLen);
  1223. }
  1224. return longest;
  1225. }
  1226. function _alignPixel(chart, pixel, width) {
  1227. const devicePixelRatio = chart.currentDevicePixelRatio;
  1228. const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
  1229. return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
  1230. }
  1231. function clearCanvas(canvas, ctx) {
  1232. ctx = ctx || canvas.getContext('2d');
  1233. ctx.save();
  1234. ctx.resetTransform();
  1235. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1236. ctx.restore();
  1237. }
  1238. function drawPoint(ctx, options, x, y) {
  1239. let type, xOffset, yOffset, size, cornerRadius;
  1240. const style = options.pointStyle;
  1241. const rotation = options.rotation;
  1242. const radius = options.radius;
  1243. let rad = (rotation || 0) * RAD_PER_DEG;
  1244. if (style && typeof style === 'object') {
  1245. type = style.toString();
  1246. if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
  1247. ctx.save();
  1248. ctx.translate(x, y);
  1249. ctx.rotate(rad);
  1250. ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
  1251. ctx.restore();
  1252. return;
  1253. }
  1254. }
  1255. if (isNaN(radius) || radius <= 0) {
  1256. return;
  1257. }
  1258. ctx.beginPath();
  1259. switch (style) {
  1260. default:
  1261. ctx.arc(x, y, radius, 0, TAU);
  1262. ctx.closePath();
  1263. break;
  1264. case 'triangle':
  1265. ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1266. rad += TWO_THIRDS_PI;
  1267. ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1268. rad += TWO_THIRDS_PI;
  1269. ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1270. ctx.closePath();
  1271. break;
  1272. case 'rectRounded':
  1273. cornerRadius = radius * 0.516;
  1274. size = radius - cornerRadius;
  1275. xOffset = Math.cos(rad + QUARTER_PI) * size;
  1276. yOffset = Math.sin(rad + QUARTER_PI) * size;
  1277. ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
  1278. ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
  1279. ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
  1280. ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
  1281. ctx.closePath();
  1282. break;
  1283. case 'rect':
  1284. if (!rotation) {
  1285. size = Math.SQRT1_2 * radius;
  1286. ctx.rect(x - size, y - size, 2 * size, 2 * size);
  1287. break;
  1288. }
  1289. rad += QUARTER_PI;
  1290. case 'rectRot':
  1291. xOffset = Math.cos(rad) * radius;
  1292. yOffset = Math.sin(rad) * radius;
  1293. ctx.moveTo(x - xOffset, y - yOffset);
  1294. ctx.lineTo(x + yOffset, y - xOffset);
  1295. ctx.lineTo(x + xOffset, y + yOffset);
  1296. ctx.lineTo(x - yOffset, y + xOffset);
  1297. ctx.closePath();
  1298. break;
  1299. case 'crossRot':
  1300. rad += QUARTER_PI;
  1301. case 'cross':
  1302. xOffset = Math.cos(rad) * radius;
  1303. yOffset = Math.sin(rad) * radius;
  1304. ctx.moveTo(x - xOffset, y - yOffset);
  1305. ctx.lineTo(x + xOffset, y + yOffset);
  1306. ctx.moveTo(x + yOffset, y - xOffset);
  1307. ctx.lineTo(x - yOffset, y + xOffset);
  1308. break;
  1309. case 'star':
  1310. xOffset = Math.cos(rad) * radius;
  1311. yOffset = Math.sin(rad) * radius;
  1312. ctx.moveTo(x - xOffset, y - yOffset);
  1313. ctx.lineTo(x + xOffset, y + yOffset);
  1314. ctx.moveTo(x + yOffset, y - xOffset);
  1315. ctx.lineTo(x - yOffset, y + xOffset);
  1316. rad += QUARTER_PI;
  1317. xOffset = Math.cos(rad) * radius;
  1318. yOffset = Math.sin(rad) * radius;
  1319. ctx.moveTo(x - xOffset, y - yOffset);
  1320. ctx.lineTo(x + xOffset, y + yOffset);
  1321. ctx.moveTo(x + yOffset, y - xOffset);
  1322. ctx.lineTo(x - yOffset, y + xOffset);
  1323. break;
  1324. case 'line':
  1325. xOffset = Math.cos(rad) * radius;
  1326. yOffset = Math.sin(rad) * radius;
  1327. ctx.moveTo(x - xOffset, y - yOffset);
  1328. ctx.lineTo(x + xOffset, y + yOffset);
  1329. break;
  1330. case 'dash':
  1331. ctx.moveTo(x, y);
  1332. ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
  1333. break;
  1334. }
  1335. ctx.fill();
  1336. if (options.borderWidth > 0) {
  1337. ctx.stroke();
  1338. }
  1339. }
  1340. function _isPointInArea(point, area, margin) {
  1341. margin = margin || 0.5;
  1342. return !area || (point && point.x > area.left - margin && point.x < area.right + margin &&
  1343. point.y > area.top - margin && point.y < area.bottom + margin);
  1344. }
  1345. function clipArea(ctx, area) {
  1346. ctx.save();
  1347. ctx.beginPath();
  1348. ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
  1349. ctx.clip();
  1350. }
  1351. function unclipArea(ctx) {
  1352. ctx.restore();
  1353. }
  1354. function _steppedLineTo(ctx, previous, target, flip, mode) {
  1355. if (!previous) {
  1356. return ctx.lineTo(target.x, target.y);
  1357. }
  1358. if (mode === 'middle') {
  1359. const midpoint = (previous.x + target.x) / 2.0;
  1360. ctx.lineTo(midpoint, previous.y);
  1361. ctx.lineTo(midpoint, target.y);
  1362. } else if (mode === 'after' !== !!flip) {
  1363. ctx.lineTo(previous.x, target.y);
  1364. } else {
  1365. ctx.lineTo(target.x, previous.y);
  1366. }
  1367. ctx.lineTo(target.x, target.y);
  1368. }
  1369. function _bezierCurveTo(ctx, previous, target, flip) {
  1370. if (!previous) {
  1371. return ctx.lineTo(target.x, target.y);
  1372. }
  1373. ctx.bezierCurveTo(
  1374. flip ? previous.cp1x : previous.cp2x,
  1375. flip ? previous.cp1y : previous.cp2y,
  1376. flip ? target.cp2x : target.cp1x,
  1377. flip ? target.cp2y : target.cp1y,
  1378. target.x,
  1379. target.y);
  1380. }
  1381. function renderText(ctx, text, x, y, font, opts = {}) {
  1382. const lines = isArray(text) ? text : [text];
  1383. const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
  1384. let i, line;
  1385. ctx.save();
  1386. ctx.font = font.string;
  1387. setRenderOpts(ctx, opts);
  1388. for (i = 0; i < lines.length; ++i) {
  1389. line = lines[i];
  1390. if (stroke) {
  1391. if (opts.strokeColor) {
  1392. ctx.strokeStyle = opts.strokeColor;
  1393. }
  1394. if (!isNullOrUndef(opts.strokeWidth)) {
  1395. ctx.lineWidth = opts.strokeWidth;
  1396. }
  1397. ctx.strokeText(line, x, y, opts.maxWidth);
  1398. }
  1399. ctx.fillText(line, x, y, opts.maxWidth);
  1400. decorateText(ctx, x, y, line, opts);
  1401. y += font.lineHeight;
  1402. }
  1403. ctx.restore();
  1404. }
  1405. function setRenderOpts(ctx, opts) {
  1406. if (opts.translation) {
  1407. ctx.translate(opts.translation[0], opts.translation[1]);
  1408. }
  1409. if (!isNullOrUndef(opts.rotation)) {
  1410. ctx.rotate(opts.rotation);
  1411. }
  1412. if (opts.color) {
  1413. ctx.fillStyle = opts.color;
  1414. }
  1415. if (opts.textAlign) {
  1416. ctx.textAlign = opts.textAlign;
  1417. }
  1418. if (opts.textBaseline) {
  1419. ctx.textBaseline = opts.textBaseline;
  1420. }
  1421. }
  1422. function decorateText(ctx, x, y, line, opts) {
  1423. if (opts.strikethrough || opts.underline) {
  1424. const metrics = ctx.measureText(line);
  1425. const left = x - metrics.actualBoundingBoxLeft;
  1426. const right = x + metrics.actualBoundingBoxRight;
  1427. const top = y - metrics.actualBoundingBoxAscent;
  1428. const bottom = y + metrics.actualBoundingBoxDescent;
  1429. const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
  1430. ctx.strokeStyle = ctx.fillStyle;
  1431. ctx.beginPath();
  1432. ctx.lineWidth = opts.decorationWidth || 2;
  1433. ctx.moveTo(left, yDecoration);
  1434. ctx.lineTo(right, yDecoration);
  1435. ctx.stroke();
  1436. }
  1437. }
  1438. function addRoundedRectPath(ctx, rect) {
  1439. const {x, y, w, h, radius} = rect;
  1440. ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);
  1441. ctx.lineTo(x, y + h - radius.bottomLeft);
  1442. ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
  1443. ctx.lineTo(x + w - radius.bottomRight, y + h);
  1444. ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
  1445. ctx.lineTo(x + w, y + radius.topRight);
  1446. ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
  1447. ctx.lineTo(x + radius.topLeft, y);
  1448. }
  1449. function _lookup(table, value, cmp) {
  1450. cmp = cmp || ((index) => table[index] < value);
  1451. let hi = table.length - 1;
  1452. let lo = 0;
  1453. let mid;
  1454. while (hi - lo > 1) {
  1455. mid = (lo + hi) >> 1;
  1456. if (cmp(mid)) {
  1457. lo = mid;
  1458. } else {
  1459. hi = mid;
  1460. }
  1461. }
  1462. return {lo, hi};
  1463. }
  1464. const _lookupByKey = (table, key, value) =>
  1465. _lookup(table, value, index => table[index][key] < value);
  1466. const _rlookupByKey = (table, key, value) =>
  1467. _lookup(table, value, index => table[index][key] >= value);
  1468. function _filterBetween(values, min, max) {
  1469. let start = 0;
  1470. let end = values.length;
  1471. while (start < end && values[start] < min) {
  1472. start++;
  1473. }
  1474. while (end > start && values[end - 1] > max) {
  1475. end--;
  1476. }
  1477. return start > 0 || end < values.length
  1478. ? values.slice(start, end)
  1479. : values;
  1480. }
  1481. const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
  1482. function listenArrayEvents(array, listener) {
  1483. if (array._chartjs) {
  1484. array._chartjs.listeners.push(listener);
  1485. return;
  1486. }
  1487. Object.defineProperty(array, '_chartjs', {
  1488. configurable: true,
  1489. enumerable: false,
  1490. value: {
  1491. listeners: [listener]
  1492. }
  1493. });
  1494. arrayEvents.forEach((key) => {
  1495. const method = '_onData' + _capitalize(key);
  1496. const base = array[key];
  1497. Object.defineProperty(array, key, {
  1498. configurable: true,
  1499. enumerable: false,
  1500. value(...args) {
  1501. const res = base.apply(this, args);
  1502. array._chartjs.listeners.forEach((object) => {
  1503. if (typeof object[method] === 'function') {
  1504. object[method](...args);
  1505. }
  1506. });
  1507. return res;
  1508. }
  1509. });
  1510. });
  1511. }
  1512. function unlistenArrayEvents(array, listener) {
  1513. const stub = array._chartjs;
  1514. if (!stub) {
  1515. return;
  1516. }
  1517. const listeners = stub.listeners;
  1518. const index = listeners.indexOf(listener);
  1519. if (index !== -1) {
  1520. listeners.splice(index, 1);
  1521. }
  1522. if (listeners.length > 0) {
  1523. return;
  1524. }
  1525. arrayEvents.forEach((key) => {
  1526. delete array[key];
  1527. });
  1528. delete array._chartjs;
  1529. }
  1530. function _arrayUnique(items) {
  1531. const set = new Set();
  1532. let i, ilen;
  1533. for (i = 0, ilen = items.length; i < ilen; ++i) {
  1534. set.add(items[i]);
  1535. }
  1536. if (set.size === ilen) {
  1537. return items;
  1538. }
  1539. return Array.from(set);
  1540. }
  1541. function _isDomSupported() {
  1542. return typeof window !== 'undefined' && typeof document !== 'undefined';
  1543. }
  1544. function _getParentNode(domNode) {
  1545. let parent = domNode.parentNode;
  1546. if (parent && parent.toString() === '[object ShadowRoot]') {
  1547. parent = parent.host;
  1548. }
  1549. return parent;
  1550. }
  1551. function parseMaxStyle(styleValue, node, parentProperty) {
  1552. let valueInPixels;
  1553. if (typeof styleValue === 'string') {
  1554. valueInPixels = parseInt(styleValue, 10);
  1555. if (styleValue.indexOf('%') !== -1) {
  1556. valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
  1557. }
  1558. } else {
  1559. valueInPixels = styleValue;
  1560. }
  1561. return valueInPixels;
  1562. }
  1563. const getComputedStyle = (element) => window.getComputedStyle(element, null);
  1564. function getStyle(el, property) {
  1565. return getComputedStyle(el).getPropertyValue(property);
  1566. }
  1567. const positions = ['top', 'right', 'bottom', 'left'];
  1568. function getPositionedStyle(styles, style, suffix) {
  1569. const result = {};
  1570. suffix = suffix ? '-' + suffix : '';
  1571. for (let i = 0; i < 4; i++) {
  1572. const pos = positions[i];
  1573. result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
  1574. }
  1575. result.width = result.left + result.right;
  1576. result.height = result.top + result.bottom;
  1577. return result;
  1578. }
  1579. const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot);
  1580. function getCanvasPosition(evt, canvas) {
  1581. const e = evt.native || evt;
  1582. const touches = e.touches;
  1583. const source = touches && touches.length ? touches[0] : e;
  1584. const {offsetX, offsetY} = source;
  1585. let box = false;
  1586. let x, y;
  1587. if (useOffsetPos(offsetX, offsetY, e.target)) {
  1588. x = offsetX;
  1589. y = offsetY;
  1590. } else {
  1591. const rect = canvas.getBoundingClientRect();
  1592. x = source.clientX - rect.left;
  1593. y = source.clientY - rect.top;
  1594. box = true;
  1595. }
  1596. return {x, y, box};
  1597. }
  1598. function getRelativePosition$1(evt, chart) {
  1599. const {canvas, currentDevicePixelRatio} = chart;
  1600. const style = getComputedStyle(canvas);
  1601. const borderBox = style.boxSizing === 'border-box';
  1602. const paddings = getPositionedStyle(style, 'padding');
  1603. const borders = getPositionedStyle(style, 'border', 'width');
  1604. const {x, y, box} = getCanvasPosition(evt, canvas);
  1605. const xOffset = paddings.left + (box && borders.left);
  1606. const yOffset = paddings.top + (box && borders.top);
  1607. let {width, height} = chart;
  1608. if (borderBox) {
  1609. width -= paddings.width + borders.width;
  1610. height -= paddings.height + borders.height;
  1611. }
  1612. return {
  1613. x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
  1614. y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
  1615. };
  1616. }
  1617. function getContainerSize(canvas, width, height) {
  1618. let maxWidth, maxHeight;
  1619. if (width === undefined || height === undefined) {
  1620. const container = _getParentNode(canvas);
  1621. if (!container) {
  1622. width = canvas.clientWidth;
  1623. height = canvas.clientHeight;
  1624. } else {
  1625. const rect = container.getBoundingClientRect();
  1626. const containerStyle = getComputedStyle(container);
  1627. const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
  1628. const containerPadding = getPositionedStyle(containerStyle, 'padding');
  1629. width = rect.width - containerPadding.width - containerBorder.width;
  1630. height = rect.height - containerPadding.height - containerBorder.height;
  1631. maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
  1632. maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
  1633. }
  1634. }
  1635. return {
  1636. width,
  1637. height,
  1638. maxWidth: maxWidth || INFINITY,
  1639. maxHeight: maxHeight || INFINITY
  1640. };
  1641. }
  1642. const round1 = v => Math.round(v * 10) / 10;
  1643. function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
  1644. const style = getComputedStyle(canvas);
  1645. const margins = getPositionedStyle(style, 'margin');
  1646. const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
  1647. const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
  1648. const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
  1649. let {width, height} = containerSize;
  1650. if (style.boxSizing === 'content-box') {
  1651. const borders = getPositionedStyle(style, 'border', 'width');
  1652. const paddings = getPositionedStyle(style, 'padding');
  1653. width -= paddings.width + borders.width;
  1654. height -= paddings.height + borders.height;
  1655. }
  1656. width = Math.max(0, width - margins.width);
  1657. height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height);
  1658. width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
  1659. height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
  1660. if (width && !height) {
  1661. height = round1(width / 2);
  1662. }
  1663. return {
  1664. width,
  1665. height
  1666. };
  1667. }
  1668. function retinaScale(chart, forceRatio, forceStyle) {
  1669. const pixelRatio = forceRatio || 1;
  1670. const deviceHeight = Math.floor(chart.height * pixelRatio);
  1671. const deviceWidth = Math.floor(chart.width * pixelRatio);
  1672. chart.height = deviceHeight / pixelRatio;
  1673. chart.width = deviceWidth / pixelRatio;
  1674. const canvas = chart.canvas;
  1675. if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {
  1676. canvas.style.height = `${chart.height}px`;
  1677. canvas.style.width = `${chart.width}px`;
  1678. }
  1679. if (chart.currentDevicePixelRatio !== pixelRatio
  1680. || canvas.height !== deviceHeight
  1681. || canvas.width !== deviceWidth) {
  1682. chart.currentDevicePixelRatio = pixelRatio;
  1683. canvas.height = deviceHeight;
  1684. canvas.width = deviceWidth;
  1685. chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
  1686. return true;
  1687. }
  1688. return false;
  1689. }
  1690. const supportsEventListenerOptions = (function() {
  1691. let passiveSupported = false;
  1692. try {
  1693. const options = {
  1694. get passive() {
  1695. passiveSupported = true;
  1696. return false;
  1697. }
  1698. };
  1699. window.addEventListener('test', null, options);
  1700. window.removeEventListener('test', null, options);
  1701. } catch (e) {
  1702. }
  1703. return passiveSupported;
  1704. }());
  1705. function readUsedSize(element, property) {
  1706. const value = getStyle(element, property);
  1707. const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
  1708. return matches ? +matches[1] : undefined;
  1709. }
  1710. function getRelativePosition(e, chart) {
  1711. if ('native' in e) {
  1712. return {
  1713. x: e.x,
  1714. y: e.y
  1715. };
  1716. }
  1717. return getRelativePosition$1(e, chart);
  1718. }
  1719. function evaluateAllVisibleItems(chart, handler) {
  1720. const metasets = chart.getSortedVisibleDatasetMetas();
  1721. let index, data, element;
  1722. for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
  1723. ({index, data} = metasets[i]);
  1724. for (let j = 0, jlen = data.length; j < jlen; ++j) {
  1725. element = data[j];
  1726. if (!element.skip) {
  1727. handler(element, index, j);
  1728. }
  1729. }
  1730. }
  1731. }
  1732. function binarySearch(metaset, axis, value, intersect) {
  1733. const {controller, data, _sorted} = metaset;
  1734. const iScale = controller._cachedMeta.iScale;
  1735. if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
  1736. const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
  1737. if (!intersect) {
  1738. return lookupMethod(data, axis, value);
  1739. } else if (controller._sharedOptions) {
  1740. const el = data[0];
  1741. const range = typeof el.getRange === 'function' && el.getRange(axis);
  1742. if (range) {
  1743. const start = lookupMethod(data, axis, value - range);
  1744. const end = lookupMethod(data, axis, value + range);
  1745. return {lo: start.lo, hi: end.hi};
  1746. }
  1747. }
  1748. }
  1749. return {lo: 0, hi: data.length - 1};
  1750. }
  1751. function optimizedEvaluateItems(chart, axis, position, handler, intersect) {
  1752. const metasets = chart.getSortedVisibleDatasetMetas();
  1753. const value = position[axis];
  1754. for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
  1755. const {index, data} = metasets[i];
  1756. const {lo, hi} = binarySearch(metasets[i], axis, value, intersect);
  1757. for (let j = lo; j <= hi; ++j) {
  1758. const element = data[j];
  1759. if (!element.skip) {
  1760. handler(element, index, j);
  1761. }
  1762. }
  1763. }
  1764. }
  1765. function getDistanceMetricForAxis(axis) {
  1766. const useX = axis.indexOf('x') !== -1;
  1767. const useY = axis.indexOf('y') !== -1;
  1768. return function(pt1, pt2) {
  1769. const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  1770. const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  1771. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  1772. };
  1773. }
  1774. function getIntersectItems(chart, position, axis, useFinalPosition) {
  1775. const items = [];
  1776. if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
  1777. return items;
  1778. }
  1779. const evaluationFunc = function(element, datasetIndex, index) {
  1780. if (element.inRange(position.x, position.y, useFinalPosition)) {
  1781. items.push({element, datasetIndex, index});
  1782. }
  1783. };
  1784. optimizedEvaluateItems(chart, axis, position, evaluationFunc, true);
  1785. return items;
  1786. }
  1787. function getNearestRadialItems(chart, position, axis, useFinalPosition) {
  1788. let items = [];
  1789. function evaluationFunc(element, datasetIndex, index) {
  1790. const {startAngle, endAngle} = element.getProps(['startAngle', 'endAngle'], useFinalPosition);
  1791. const {angle} = getAngleFromPoint(element, {x: position.x, y: position.y});
  1792. if (_angleBetween(angle, startAngle, endAngle)) {
  1793. items.push({element, datasetIndex, index});
  1794. }
  1795. }
  1796. optimizedEvaluateItems(chart, axis, position, evaluationFunc);
  1797. return items;
  1798. }
  1799. function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) {
  1800. let items = [];
  1801. const distanceMetric = getDistanceMetricForAxis(axis);
  1802. let minDistance = Number.POSITIVE_INFINITY;
  1803. function evaluationFunc(element, datasetIndex, index) {
  1804. const inRange = element.inRange(position.x, position.y, useFinalPosition);
  1805. if (intersect && !inRange) {
  1806. return;
  1807. }
  1808. const center = element.getCenterPoint(useFinalPosition);
  1809. const pointInArea = _isPointInArea(center, chart.chartArea, chart._minPadding);
  1810. if (!pointInArea && !inRange) {
  1811. return;
  1812. }
  1813. const distance = distanceMetric(position, center);
  1814. if (distance < minDistance) {
  1815. items = [{element, datasetIndex, index}];
  1816. minDistance = distance;
  1817. } else if (distance === minDistance) {
  1818. items.push({element, datasetIndex, index});
  1819. }
  1820. }
  1821. optimizedEvaluateItems(chart, axis, position, evaluationFunc);
  1822. return items;
  1823. }
  1824. function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
  1825. if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
  1826. return [];
  1827. }
  1828. return axis === 'r' && !intersect
  1829. ? getNearestRadialItems(chart, position, axis, useFinalPosition)
  1830. : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition);
  1831. }
  1832. function getAxisItems(chart, e, options, useFinalPosition) {
  1833. const position = getRelativePosition(e, chart);
  1834. const items = [];
  1835. const axis = options.axis;
  1836. const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
  1837. let intersectsItem = false;
  1838. evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
  1839. if (element[rangeMethod](position[axis], useFinalPosition)) {
  1840. items.push({element, datasetIndex, index});
  1841. }
  1842. if (element.inRange(position.x, position.y, useFinalPosition)) {
  1843. intersectsItem = true;
  1844. }
  1845. });
  1846. if (options.intersect && !intersectsItem) {
  1847. return [];
  1848. }
  1849. return items;
  1850. }
  1851. var Interaction = {
  1852. modes: {
  1853. index(chart, e, options, useFinalPosition) {
  1854. const position = getRelativePosition(e, chart);
  1855. const axis = options.axis || 'x';
  1856. const items = options.intersect
  1857. ? getIntersectItems(chart, position, axis, useFinalPosition)
  1858. : getNearestItems(chart, position, axis, false, useFinalPosition);
  1859. const elements = [];
  1860. if (!items.length) {
  1861. return [];
  1862. }
  1863. chart.getSortedVisibleDatasetMetas().forEach((meta) => {
  1864. const index = items[0].index;
  1865. const element = meta.data[index];
  1866. if (element && !element.skip) {
  1867. elements.push({element, datasetIndex: meta.index, index});
  1868. }
  1869. });
  1870. return elements;
  1871. },
  1872. dataset(chart, e, options, useFinalPosition) {
  1873. const position = getRelativePosition(e, chart);
  1874. const axis = options.axis || 'xy';
  1875. let items = options.intersect
  1876. ? getIntersectItems(chart, position, axis, useFinalPosition) :
  1877. getNearestItems(chart, position, axis, false, useFinalPosition);
  1878. if (items.length > 0) {
  1879. const datasetIndex = items[0].datasetIndex;
  1880. const data = chart.getDatasetMeta(datasetIndex).data;
  1881. items = [];
  1882. for (let i = 0; i < data.length; ++i) {
  1883. items.push({element: data[i], datasetIndex, index: i});
  1884. }
  1885. }
  1886. return items;
  1887. },
  1888. point(chart, e, options, useFinalPosition) {
  1889. const position = getRelativePosition(e, chart);
  1890. const axis = options.axis || 'xy';
  1891. return getIntersectItems(chart, position, axis, useFinalPosition);
  1892. },
  1893. nearest(chart, e, options, useFinalPosition) {
  1894. const position = getRelativePosition(e, chart);
  1895. const axis = options.axis || 'xy';
  1896. return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
  1897. },
  1898. x(chart, e, options, useFinalPosition) {
  1899. return getAxisItems(chart, e, {axis: 'x', intersect: options.intersect}, useFinalPosition);
  1900. },
  1901. y(chart, e, options, useFinalPosition) {
  1902. return getAxisItems(chart, e, {axis: 'y', intersect: options.intersect}, useFinalPosition);
  1903. }
  1904. }
  1905. };
  1906. const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
  1907. const FONT_STYLE = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);
  1908. function toLineHeight(value, size) {
  1909. const matches = ('' + value).match(LINE_HEIGHT);
  1910. if (!matches || matches[1] === 'normal') {
  1911. return size * 1.2;
  1912. }
  1913. value = +matches[2];
  1914. switch (matches[3]) {
  1915. case 'px':
  1916. return value;
  1917. case '%':
  1918. value /= 100;
  1919. break;
  1920. }
  1921. return size * value;
  1922. }
  1923. const numberOrZero = v => +v || 0;
  1924. function _readValueToProps(value, props) {
  1925. const ret = {};
  1926. const objProps = isObject(props);
  1927. const keys = objProps ? Object.keys(props) : props;
  1928. const read = isObject(value)
  1929. ? objProps
  1930. ? prop => valueOrDefault(value[prop], value[props[prop]])
  1931. : prop => value[prop]
  1932. : () => value;
  1933. for (const prop of keys) {
  1934. ret[prop] = numberOrZero(read(prop));
  1935. }
  1936. return ret;
  1937. }
  1938. function toTRBL(value) {
  1939. return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'});
  1940. }
  1941. function toTRBLCorners(value) {
  1942. return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);
  1943. }
  1944. function toPadding(value) {
  1945. const obj = toTRBL(value);
  1946. obj.width = obj.left + obj.right;
  1947. obj.height = obj.top + obj.bottom;
  1948. return obj;
  1949. }
  1950. function toFont(options, fallback) {
  1951. options = options || {};
  1952. fallback = fallback || defaults.font;
  1953. let size = valueOrDefault(options.size, fallback.size);
  1954. if (typeof size === 'string') {
  1955. size = parseInt(size, 10);
  1956. }
  1957. let style = valueOrDefault(options.style, fallback.style);
  1958. if (style && !('' + style).match(FONT_STYLE)) {
  1959. console.warn('Invalid font style specified: "' + style + '"');
  1960. style = '';
  1961. }
  1962. const font = {
  1963. family: valueOrDefault(options.family, fallback.family),
  1964. lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
  1965. size,
  1966. style,
  1967. weight: valueOrDefault(options.weight, fallback.weight),
  1968. string: ''
  1969. };
  1970. font.string = toFontString(font);
  1971. return font;
  1972. }
  1973. function resolve(inputs, context, index, info) {
  1974. let cacheable = true;
  1975. let i, ilen, value;
  1976. for (i = 0, ilen = inputs.length; i < ilen; ++i) {
  1977. value = inputs[i];
  1978. if (value === undefined) {
  1979. continue;
  1980. }
  1981. if (context !== undefined && typeof value === 'function') {
  1982. value = value(context);
  1983. cacheable = false;
  1984. }
  1985. if (index !== undefined && isArray(value)) {
  1986. value = value[index % value.length];
  1987. cacheable = false;
  1988. }
  1989. if (value !== undefined) {
  1990. if (info && !cacheable) {
  1991. info.cacheable = false;
  1992. }
  1993. return value;
  1994. }
  1995. }
  1996. }
  1997. function _addGrace(minmax, grace, beginAtZero) {
  1998. const {min, max} = minmax;
  1999. const change = toDimension(grace, (max - min) / 2);
  2000. const keepZero = (value, add) => beginAtZero && value === 0 ? 0 : value + add;
  2001. return {
  2002. min: keepZero(min, -Math.abs(change)),
  2003. max: keepZero(max, change)
  2004. };
  2005. }
  2006. function createContext(parentContext, context) {
  2007. return Object.assign(Object.create(parentContext), context);
  2008. }
  2009. const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
  2010. function filterByPosition(array, position) {
  2011. return array.filter(v => v.pos === position);
  2012. }
  2013. function filterDynamicPositionByAxis(array, axis) {
  2014. return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
  2015. }
  2016. function sortByWeight(array, reverse) {
  2017. return array.sort((a, b) => {
  2018. const v0 = reverse ? b : a;
  2019. const v1 = reverse ? a : b;
  2020. return v0.weight === v1.weight ?
  2021. v0.index - v1.index :
  2022. v0.weight - v1.weight;
  2023. });
  2024. }
  2025. function wrapBoxes(boxes) {
  2026. const layoutBoxes = [];
  2027. let i, ilen, box, pos, stack, stackWeight;
  2028. for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
  2029. box = boxes[i];
  2030. ({position: pos, options: {stack, stackWeight = 1}} = box);
  2031. layoutBoxes.push({
  2032. index: i,
  2033. box,
  2034. pos,
  2035. horizontal: box.isHorizontal(),
  2036. weight: box.weight,
  2037. stack: stack && (pos + stack),
  2038. stackWeight
  2039. });
  2040. }
  2041. return layoutBoxes;
  2042. }
  2043. function buildStacks(layouts) {
  2044. const stacks = {};
  2045. for (const wrap of layouts) {
  2046. const {stack, pos, stackWeight} = wrap;
  2047. if (!stack || !STATIC_POSITIONS.includes(pos)) {
  2048. continue;
  2049. }
  2050. const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0});
  2051. _stack.count++;
  2052. _stack.weight += stackWeight;
  2053. }
  2054. return stacks;
  2055. }
  2056. function setLayoutDims(layouts, params) {
  2057. const stacks = buildStacks(layouts);
  2058. const {vBoxMaxWidth, hBoxMaxHeight} = params;
  2059. let i, ilen, layout;
  2060. for (i = 0, ilen = layouts.length; i < ilen; ++i) {
  2061. layout = layouts[i];
  2062. const {fullSize} = layout.box;
  2063. const stack = stacks[layout.stack];
  2064. const factor = stack && layout.stackWeight / stack.weight;
  2065. if (layout.horizontal) {
  2066. layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
  2067. layout.height = hBoxMaxHeight;
  2068. } else {
  2069. layout.width = vBoxMaxWidth;
  2070. layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
  2071. }
  2072. }
  2073. return stacks;
  2074. }
  2075. function buildLayoutBoxes(boxes) {
  2076. const layoutBoxes = wrapBoxes(boxes);
  2077. const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true);
  2078. const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
  2079. const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
  2080. const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
  2081. const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
  2082. const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
  2083. const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
  2084. return {
  2085. fullSize,
  2086. leftAndTop: left.concat(top),
  2087. rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
  2088. chartArea: filterByPosition(layoutBoxes, 'chartArea'),
  2089. vertical: left.concat(right).concat(centerVertical),
  2090. horizontal: top.concat(bottom).concat(centerHorizontal)
  2091. };
  2092. }
  2093. function getCombinedMax(maxPadding, chartArea, a, b) {
  2094. return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  2095. }
  2096. function updateMaxPadding(maxPadding, boxPadding) {
  2097. maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
  2098. maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
  2099. maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
  2100. maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  2101. }
  2102. function updateDims(chartArea, params, layout, stacks) {
  2103. const {pos, box} = layout;
  2104. const maxPadding = chartArea.maxPadding;
  2105. if (!isObject(pos)) {
  2106. if (layout.size) {
  2107. chartArea[pos] -= layout.size;
  2108. }
  2109. const stack = stacks[layout.stack] || {size: 0, count: 1};
  2110. stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
  2111. layout.size = stack.size / stack.count;
  2112. chartArea[pos] += layout.size;
  2113. }
  2114. if (box.getPadding) {
  2115. updateMaxPadding(maxPadding, box.getPadding());
  2116. }
  2117. const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
  2118. const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
  2119. const widthChanged = newWidth !== chartArea.w;
  2120. const heightChanged = newHeight !== chartArea.h;
  2121. chartArea.w = newWidth;
  2122. chartArea.h = newHeight;
  2123. return layout.horizontal
  2124. ? {same: widthChanged, other: heightChanged}
  2125. : {same: heightChanged, other: widthChanged};
  2126. }
  2127. function handleMaxPadding(chartArea) {
  2128. const maxPadding = chartArea.maxPadding;
  2129. function updatePos(pos) {
  2130. const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
  2131. chartArea[pos] += change;
  2132. return change;
  2133. }
  2134. chartArea.y += updatePos('top');
  2135. chartArea.x += updatePos('left');
  2136. updatePos('right');
  2137. updatePos('bottom');
  2138. }
  2139. function getMargins(horizontal, chartArea) {
  2140. const maxPadding = chartArea.maxPadding;
  2141. function marginForPositions(positions) {
  2142. const margin = {left: 0, top: 0, right: 0, bottom: 0};
  2143. positions.forEach((pos) => {
  2144. margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
  2145. });
  2146. return margin;
  2147. }
  2148. return horizontal
  2149. ? marginForPositions(['left', 'right'])
  2150. : marginForPositions(['top', 'bottom']);
  2151. }
  2152. function fitBoxes(boxes, chartArea, params, stacks) {
  2153. const refitBoxes = [];
  2154. let i, ilen, layout, box, refit, changed;
  2155. for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {
  2156. layout = boxes[i];
  2157. box = layout.box;
  2158. box.update(
  2159. layout.width || chartArea.w,
  2160. layout.height || chartArea.h,
  2161. getMargins(layout.horizontal, chartArea)
  2162. );
  2163. const {same, other} = updateDims(chartArea, params, layout, stacks);
  2164. refit |= same && refitBoxes.length;
  2165. changed = changed || other;
  2166. if (!box.fullSize) {
  2167. refitBoxes.push(layout);
  2168. }
  2169. }
  2170. return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  2171. }
  2172. function setBoxDims(box, left, top, width, height) {
  2173. box.top = top;
  2174. box.left = left;
  2175. box.right = left + width;
  2176. box.bottom = top + height;
  2177. box.width = width;
  2178. box.height = height;
  2179. }
  2180. function placeBoxes(boxes, chartArea, params, stacks) {
  2181. const userPadding = params.padding;
  2182. let {x, y} = chartArea;
  2183. for (const layout of boxes) {
  2184. const box = layout.box;
  2185. const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1};
  2186. const weight = (layout.stackWeight / stack.weight) || 1;
  2187. if (layout.horizontal) {
  2188. const width = chartArea.w * weight;
  2189. const height = stack.size || box.height;
  2190. if (defined(stack.start)) {
  2191. y = stack.start;
  2192. }
  2193. if (box.fullSize) {
  2194. setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
  2195. } else {
  2196. setBoxDims(box, chartArea.left + stack.placed, y, width, height);
  2197. }
  2198. stack.start = y;
  2199. stack.placed += width;
  2200. y = box.bottom;
  2201. } else {
  2202. const height = chartArea.h * weight;
  2203. const width = stack.size || box.width;
  2204. if (defined(stack.start)) {
  2205. x = stack.start;
  2206. }
  2207. if (box.fullSize) {
  2208. setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
  2209. } else {
  2210. setBoxDims(box, x, chartArea.top + stack.placed, width, height);
  2211. }
  2212. stack.start = x;
  2213. stack.placed += height;
  2214. x = box.right;
  2215. }
  2216. }
  2217. chartArea.x = x;
  2218. chartArea.y = y;
  2219. }
  2220. defaults.set('layout', {
  2221. autoPadding: true,
  2222. padding: {
  2223. top: 0,
  2224. right: 0,
  2225. bottom: 0,
  2226. left: 0
  2227. }
  2228. });
  2229. var layouts = {
  2230. addBox(chart, item) {
  2231. if (!chart.boxes) {
  2232. chart.boxes = [];
  2233. }
  2234. item.fullSize = item.fullSize || false;
  2235. item.position = item.position || 'top';
  2236. item.weight = item.weight || 0;
  2237. item._layers = item._layers || function() {
  2238. return [{
  2239. z: 0,
  2240. draw(chartArea) {
  2241. item.draw(chartArea);
  2242. }
  2243. }];
  2244. };
  2245. chart.boxes.push(item);
  2246. },
  2247. removeBox(chart, layoutItem) {
  2248. const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
  2249. if (index !== -1) {
  2250. chart.boxes.splice(index, 1);
  2251. }
  2252. },
  2253. configure(chart, item, options) {
  2254. item.fullSize = options.fullSize;
  2255. item.position = options.position;
  2256. item.weight = options.weight;
  2257. },
  2258. update(chart, width, height, minPadding) {
  2259. if (!chart) {
  2260. return;
  2261. }
  2262. const padding = toPadding(chart.options.layout.padding);
  2263. const availableWidth = Math.max(width - padding.width, 0);
  2264. const availableHeight = Math.max(height - padding.height, 0);
  2265. const boxes = buildLayoutBoxes(chart.boxes);
  2266. const verticalBoxes = boxes.vertical;
  2267. const horizontalBoxes = boxes.horizontal;
  2268. each(chart.boxes, box => {
  2269. if (typeof box.beforeLayout === 'function') {
  2270. box.beforeLayout();
  2271. }
  2272. });
  2273. const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) =>
  2274. wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
  2275. const params = Object.freeze({
  2276. outerWidth: width,
  2277. outerHeight: height,
  2278. padding,
  2279. availableWidth,
  2280. availableHeight,
  2281. vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
  2282. hBoxMaxHeight: availableHeight / 2
  2283. });
  2284. const maxPadding = Object.assign({}, padding);
  2285. updateMaxPadding(maxPadding, toPadding(minPadding));
  2286. const chartArea = Object.assign({
  2287. maxPadding,
  2288. w: availableWidth,
  2289. h: availableHeight,
  2290. x: padding.left,
  2291. y: padding.top
  2292. }, padding);
  2293. const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
  2294. fitBoxes(boxes.fullSize, chartArea, params, stacks);
  2295. fitBoxes(verticalBoxes, chartArea, params, stacks);
  2296. if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
  2297. fitBoxes(verticalBoxes, chartArea, params, stacks);
  2298. }
  2299. handleMaxPadding(chartArea);
  2300. placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
  2301. chartArea.x += chartArea.w;
  2302. chartArea.y += chartArea.h;
  2303. placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
  2304. chart.chartArea = {
  2305. left: chartArea.left,
  2306. top: chartArea.top,
  2307. right: chartArea.left + chartArea.w,
  2308. bottom: chartArea.top + chartArea.h,
  2309. height: chartArea.h,
  2310. width: chartArea.w,
  2311. };
  2312. each(boxes.chartArea, (layout) => {
  2313. const box = layout.box;
  2314. Object.assign(box, chart.chartArea);
  2315. box.update(chartArea.w, chartArea.h, {left: 0, top: 0, right: 0, bottom: 0});
  2316. });
  2317. }
  2318. };
  2319. function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) {
  2320. if (!defined(fallback)) {
  2321. fallback = _resolve('_fallback', scopes);
  2322. }
  2323. const cache = {
  2324. [Symbol.toStringTag]: 'Object',
  2325. _cacheable: true,
  2326. _scopes: scopes,
  2327. _rootScopes: rootScopes,
  2328. _fallback: fallback,
  2329. _getTarget: getTarget,
  2330. override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback),
  2331. };
  2332. return new Proxy(cache, {
  2333. deleteProperty(target, prop) {
  2334. delete target[prop];
  2335. delete target._keys;
  2336. delete scopes[0][prop];
  2337. return true;
  2338. },
  2339. get(target, prop) {
  2340. return _cached(target, prop,
  2341. () => _resolveWithPrefixes(prop, prefixes, scopes, target));
  2342. },
  2343. getOwnPropertyDescriptor(target, prop) {
  2344. return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
  2345. },
  2346. getPrototypeOf() {
  2347. return Reflect.getPrototypeOf(scopes[0]);
  2348. },
  2349. has(target, prop) {
  2350. return getKeysFromAllScopes(target).includes(prop);
  2351. },
  2352. ownKeys(target) {
  2353. return getKeysFromAllScopes(target);
  2354. },
  2355. set(target, prop, value) {
  2356. const storage = target._storage || (target._storage = getTarget());
  2357. target[prop] = storage[prop] = value;
  2358. delete target._keys;
  2359. return true;
  2360. }
  2361. });
  2362. }
  2363. function _attachContext(proxy, context, subProxy, descriptorDefaults) {
  2364. const cache = {
  2365. _cacheable: false,
  2366. _proxy: proxy,
  2367. _context: context,
  2368. _subProxy: subProxy,
  2369. _stack: new Set(),
  2370. _descriptors: _descriptors(proxy, descriptorDefaults),
  2371. setContext: (ctx) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),
  2372. override: (scope) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
  2373. };
  2374. return new Proxy(cache, {
  2375. deleteProperty(target, prop) {
  2376. delete target[prop];
  2377. delete proxy[prop];
  2378. return true;
  2379. },
  2380. get(target, prop, receiver) {
  2381. return _cached(target, prop,
  2382. () => _resolveWithContext(target, prop, receiver));
  2383. },
  2384. getOwnPropertyDescriptor(target, prop) {
  2385. return target._descriptors.allKeys
  2386. ? Reflect.has(proxy, prop) ? {enumerable: true, configurable: true} : undefined
  2387. : Reflect.getOwnPropertyDescriptor(proxy, prop);
  2388. },
  2389. getPrototypeOf() {
  2390. return Reflect.getPrototypeOf(proxy);
  2391. },
  2392. has(target, prop) {
  2393. return Reflect.has(proxy, prop);
  2394. },
  2395. ownKeys() {
  2396. return Reflect.ownKeys(proxy);
  2397. },
  2398. set(target, prop, value) {
  2399. proxy[prop] = value;
  2400. delete target[prop];
  2401. return true;
  2402. }
  2403. });
  2404. }
  2405. function _descriptors(proxy, defaults = {scriptable: true, indexable: true}) {
  2406. const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
  2407. return {
  2408. allKeys: _allKeys,
  2409. scriptable: _scriptable,
  2410. indexable: _indexable,
  2411. isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,
  2412. isIndexable: isFunction(_indexable) ? _indexable : () => _indexable
  2413. };
  2414. }
  2415. const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name;
  2416. const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' &&
  2417. (Object.getPrototypeOf(value) === null || value.constructor === Object);
  2418. function _cached(target, prop, resolve) {
  2419. if (Object.prototype.hasOwnProperty.call(target, prop)) {
  2420. return target[prop];
  2421. }
  2422. const value = resolve();
  2423. target[prop] = value;
  2424. return value;
  2425. }
  2426. function _resolveWithContext(target, prop, receiver) {
  2427. const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  2428. let value = _proxy[prop];
  2429. if (isFunction(value) && descriptors.isScriptable(prop)) {
  2430. value = _resolveScriptable(prop, value, target, receiver);
  2431. }
  2432. if (isArray(value) && value.length) {
  2433. value = _resolveArray(prop, value, target, descriptors.isIndexable);
  2434. }
  2435. if (needsSubResolver(prop, value)) {
  2436. value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
  2437. }
  2438. return value;
  2439. }
  2440. function _resolveScriptable(prop, value, target, receiver) {
  2441. const {_proxy, _context, _subProxy, _stack} = target;
  2442. if (_stack.has(prop)) {
  2443. throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
  2444. }
  2445. _stack.add(prop);
  2446. value = value(_context, _subProxy || receiver);
  2447. _stack.delete(prop);
  2448. if (needsSubResolver(prop, value)) {
  2449. value = createSubResolver(_proxy._scopes, _proxy, prop, value);
  2450. }
  2451. return value;
  2452. }
  2453. function _resolveArray(prop, value, target, isIndexable) {
  2454. const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  2455. if (defined(_context.index) && isIndexable(prop)) {
  2456. value = value[_context.index % value.length];
  2457. } else if (isObject(value[0])) {
  2458. const arr = value;
  2459. const scopes = _proxy._scopes.filter(s => s !== arr);
  2460. value = [];
  2461. for (const item of arr) {
  2462. const resolver = createSubResolver(scopes, _proxy, prop, item);
  2463. value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
  2464. }
  2465. }
  2466. return value;
  2467. }
  2468. function resolveFallback(fallback, prop, value) {
  2469. return isFunction(fallback) ? fallback(prop, value) : fallback;
  2470. }
  2471. const getScope = (key, parent) => key === true ? parent
  2472. : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
  2473. function addScopes(set, parentScopes, key, parentFallback, value) {
  2474. for (const parent of parentScopes) {
  2475. const scope = getScope(key, parent);
  2476. if (scope) {
  2477. set.add(scope);
  2478. const fallback = resolveFallback(scope._fallback, key, value);
  2479. if (defined(fallback) && fallback !== key && fallback !== parentFallback) {
  2480. return fallback;
  2481. }
  2482. } else if (scope === false && defined(parentFallback) && key !== parentFallback) {
  2483. return null;
  2484. }
  2485. }
  2486. return false;
  2487. }
  2488. function createSubResolver(parentScopes, resolver, prop, value) {
  2489. const rootScopes = resolver._rootScopes;
  2490. const fallback = resolveFallback(resolver._fallback, prop, value);
  2491. const allScopes = [...parentScopes, ...rootScopes];
  2492. const set = new Set();
  2493. set.add(value);
  2494. let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
  2495. if (key === null) {
  2496. return false;
  2497. }
  2498. if (defined(fallback) && fallback !== prop) {
  2499. key = addScopesFromKey(set, allScopes, fallback, key, value);
  2500. if (key === null) {
  2501. return false;
  2502. }
  2503. }
  2504. return _createResolver(Array.from(set), [''], rootScopes, fallback,
  2505. () => subGetTarget(resolver, prop, value));
  2506. }
  2507. function addScopesFromKey(set, allScopes, key, fallback, item) {
  2508. while (key) {
  2509. key = addScopes(set, allScopes, key, fallback, item);
  2510. }
  2511. return key;
  2512. }
  2513. function subGetTarget(resolver, prop, value) {
  2514. const parent = resolver._getTarget();
  2515. if (!(prop in parent)) {
  2516. parent[prop] = {};
  2517. }
  2518. const target = parent[prop];
  2519. if (isArray(target) && isObject(value)) {
  2520. return value;
  2521. }
  2522. return target;
  2523. }
  2524. function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
  2525. let value;
  2526. for (const prefix of prefixes) {
  2527. value = _resolve(readKey(prefix, prop), scopes);
  2528. if (defined(value)) {
  2529. return needsSubResolver(prop, value)
  2530. ? createSubResolver(scopes, proxy, prop, value)
  2531. : value;
  2532. }
  2533. }
  2534. }
  2535. function _resolve(key, scopes) {
  2536. for (const scope of scopes) {
  2537. if (!scope) {
  2538. continue;
  2539. }
  2540. const value = scope[key];
  2541. if (defined(value)) {
  2542. return value;
  2543. }
  2544. }
  2545. }
  2546. function getKeysFromAllScopes(target) {
  2547. let keys = target._keys;
  2548. if (!keys) {
  2549. keys = target._keys = resolveKeysFromAllScopes(target._scopes);
  2550. }
  2551. return keys;
  2552. }
  2553. function resolveKeysFromAllScopes(scopes) {
  2554. const set = new Set();
  2555. for (const scope of scopes) {
  2556. for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) {
  2557. set.add(key);
  2558. }
  2559. }
  2560. return Array.from(set);
  2561. }
  2562. const EPSILON = Number.EPSILON || 1e-14;
  2563. const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
  2564. const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';
  2565. function splineCurve(firstPoint, middlePoint, afterPoint, t) {
  2566. const previous = firstPoint.skip ? middlePoint : firstPoint;
  2567. const current = middlePoint;
  2568. const next = afterPoint.skip ? middlePoint : afterPoint;
  2569. const d01 = distanceBetweenPoints(current, previous);
  2570. const d12 = distanceBetweenPoints(next, current);
  2571. let s01 = d01 / (d01 + d12);
  2572. let s12 = d12 / (d01 + d12);
  2573. s01 = isNaN(s01) ? 0 : s01;
  2574. s12 = isNaN(s12) ? 0 : s12;
  2575. const fa = t * s01;
  2576. const fb = t * s12;
  2577. return {
  2578. previous: {
  2579. x: current.x - fa * (next.x - previous.x),
  2580. y: current.y - fa * (next.y - previous.y)
  2581. },
  2582. next: {
  2583. x: current.x + fb * (next.x - previous.x),
  2584. y: current.y + fb * (next.y - previous.y)
  2585. }
  2586. };
  2587. }
  2588. function monotoneAdjust(points, deltaK, mK) {
  2589. const pointsLen = points.length;
  2590. let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
  2591. let pointAfter = getPoint(points, 0);
  2592. for (let i = 0; i < pointsLen - 1; ++i) {
  2593. pointCurrent = pointAfter;
  2594. pointAfter = getPoint(points, i + 1);
  2595. if (!pointCurrent || !pointAfter) {
  2596. continue;
  2597. }
  2598. if (almostEquals(deltaK[i], 0, EPSILON)) {
  2599. mK[i] = mK[i + 1] = 0;
  2600. continue;
  2601. }
  2602. alphaK = mK[i] / deltaK[i];
  2603. betaK = mK[i + 1] / deltaK[i];
  2604. squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
  2605. if (squaredMagnitude <= 9) {
  2606. continue;
  2607. }
  2608. tauK = 3 / Math.sqrt(squaredMagnitude);
  2609. mK[i] = alphaK * tauK * deltaK[i];
  2610. mK[i + 1] = betaK * tauK * deltaK[i];
  2611. }
  2612. }
  2613. function monotoneCompute(points, mK, indexAxis = 'x') {
  2614. const valueAxis = getValueAxis(indexAxis);
  2615. const pointsLen = points.length;
  2616. let delta, pointBefore, pointCurrent;
  2617. let pointAfter = getPoint(points, 0);
  2618. for (let i = 0; i < pointsLen; ++i) {
  2619. pointBefore = pointCurrent;
  2620. pointCurrent = pointAfter;
  2621. pointAfter = getPoint(points, i + 1);
  2622. if (!pointCurrent) {
  2623. continue;
  2624. }
  2625. const iPixel = pointCurrent[indexAxis];
  2626. const vPixel = pointCurrent[valueAxis];
  2627. if (pointBefore) {
  2628. delta = (iPixel - pointBefore[indexAxis]) / 3;
  2629. pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
  2630. pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
  2631. }
  2632. if (pointAfter) {
  2633. delta = (pointAfter[indexAxis] - iPixel) / 3;
  2634. pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
  2635. pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
  2636. }
  2637. }
  2638. }
  2639. function splineCurveMonotone(points, indexAxis = 'x') {
  2640. const valueAxis = getValueAxis(indexAxis);
  2641. const pointsLen = points.length;
  2642. const deltaK = Array(pointsLen).fill(0);
  2643. const mK = Array(pointsLen);
  2644. let i, pointBefore, pointCurrent;
  2645. let pointAfter = getPoint(points, 0);
  2646. for (i = 0; i < pointsLen; ++i) {
  2647. pointBefore = pointCurrent;
  2648. pointCurrent = pointAfter;
  2649. pointAfter = getPoint(points, i + 1);
  2650. if (!pointCurrent) {
  2651. continue;
  2652. }
  2653. if (pointAfter) {
  2654. const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
  2655. deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
  2656. }
  2657. mK[i] = !pointBefore ? deltaK[i]
  2658. : !pointAfter ? deltaK[i - 1]
  2659. : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0
  2660. : (deltaK[i - 1] + deltaK[i]) / 2;
  2661. }
  2662. monotoneAdjust(points, deltaK, mK);
  2663. monotoneCompute(points, mK, indexAxis);
  2664. }
  2665. function capControlPoint(pt, min, max) {
  2666. return Math.max(Math.min(pt, max), min);
  2667. }
  2668. function capBezierPoints(points, area) {
  2669. let i, ilen, point, inArea, inAreaPrev;
  2670. let inAreaNext = _isPointInArea(points[0], area);
  2671. for (i = 0, ilen = points.length; i < ilen; ++i) {
  2672. inAreaPrev = inArea;
  2673. inArea = inAreaNext;
  2674. inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
  2675. if (!inArea) {
  2676. continue;
  2677. }
  2678. point = points[i];
  2679. if (inAreaPrev) {
  2680. point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
  2681. point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
  2682. }
  2683. if (inAreaNext) {
  2684. point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
  2685. point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
  2686. }
  2687. }
  2688. }
  2689. function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
  2690. let i, ilen, point, controlPoints;
  2691. if (options.spanGaps) {
  2692. points = points.filter((pt) => !pt.skip);
  2693. }
  2694. if (options.cubicInterpolationMode === 'monotone') {
  2695. splineCurveMonotone(points, indexAxis);
  2696. } else {
  2697. let prev = loop ? points[points.length - 1] : points[0];
  2698. for (i = 0, ilen = points.length; i < ilen; ++i) {
  2699. point = points[i];
  2700. controlPoints = splineCurve(
  2701. prev,
  2702. point,
  2703. points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen],
  2704. options.tension
  2705. );
  2706. point.cp1x = controlPoints.previous.x;
  2707. point.cp1y = controlPoints.previous.y;
  2708. point.cp2x = controlPoints.next.x;
  2709. point.cp2y = controlPoints.next.y;
  2710. prev = point;
  2711. }
  2712. }
  2713. if (options.capBezierPoints) {
  2714. capBezierPoints(points, area);
  2715. }
  2716. }
  2717. const atEdge = (t) => t === 0 || t === 1;
  2718. const elasticIn = (t, s, p) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
  2719. const elasticOut = (t, s, p) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
  2720. const effects = {
  2721. linear: t => t,
  2722. easeInQuad: t => t * t,
  2723. easeOutQuad: t => -t * (t - 2),
  2724. easeInOutQuad: t => ((t /= 0.5) < 1)
  2725. ? 0.5 * t * t
  2726. : -0.5 * ((--t) * (t - 2) - 1),
  2727. easeInCubic: t => t * t * t,
  2728. easeOutCubic: t => (t -= 1) * t * t + 1,
  2729. easeInOutCubic: t => ((t /= 0.5) < 1)
  2730. ? 0.5 * t * t * t
  2731. : 0.5 * ((t -= 2) * t * t + 2),
  2732. easeInQuart: t => t * t * t * t,
  2733. easeOutQuart: t => -((t -= 1) * t * t * t - 1),
  2734. easeInOutQuart: t => ((t /= 0.5) < 1)
  2735. ? 0.5 * t * t * t * t
  2736. : -0.5 * ((t -= 2) * t * t * t - 2),
  2737. easeInQuint: t => t * t * t * t * t,
  2738. easeOutQuint: t => (t -= 1) * t * t * t * t + 1,
  2739. easeInOutQuint: t => ((t /= 0.5) < 1)
  2740. ? 0.5 * t * t * t * t * t
  2741. : 0.5 * ((t -= 2) * t * t * t * t + 2),
  2742. easeInSine: t => -Math.cos(t * HALF_PI) + 1,
  2743. easeOutSine: t => Math.sin(t * HALF_PI),
  2744. easeInOutSine: t => -0.5 * (Math.cos(PI * t) - 1),
  2745. easeInExpo: t => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)),
  2746. easeOutExpo: t => (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1,
  2747. easeInOutExpo: t => atEdge(t) ? t : t < 0.5
  2748. ? 0.5 * Math.pow(2, 10 * (t * 2 - 1))
  2749. : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
  2750. easeInCirc: t => (t >= 1) ? t : -(Math.sqrt(1 - t * t) - 1),
  2751. easeOutCirc: t => Math.sqrt(1 - (t -= 1) * t),
  2752. easeInOutCirc: t => ((t /= 0.5) < 1)
  2753. ? -0.5 * (Math.sqrt(1 - t * t) - 1)
  2754. : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
  2755. easeInElastic: t => atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
  2756. easeOutElastic: t => atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
  2757. easeInOutElastic(t) {
  2758. const s = 0.1125;
  2759. const p = 0.45;
  2760. return atEdge(t) ? t :
  2761. t < 0.5
  2762. ? 0.5 * elasticIn(t * 2, s, p)
  2763. : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
  2764. },
  2765. easeInBack(t) {
  2766. const s = 1.70158;
  2767. return t * t * ((s + 1) * t - s);
  2768. },
  2769. easeOutBack(t) {
  2770. const s = 1.70158;
  2771. return (t -= 1) * t * ((s + 1) * t + s) + 1;
  2772. },
  2773. easeInOutBack(t) {
  2774. let s = 1.70158;
  2775. if ((t /= 0.5) < 1) {
  2776. return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
  2777. }
  2778. return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
  2779. },
  2780. easeInBounce: t => 1 - effects.easeOutBounce(1 - t),
  2781. easeOutBounce(t) {
  2782. const m = 7.5625;
  2783. const d = 2.75;
  2784. if (t < (1 / d)) {
  2785. return m * t * t;
  2786. }
  2787. if (t < (2 / d)) {
  2788. return m * (t -= (1.5 / d)) * t + 0.75;
  2789. }
  2790. if (t < (2.5 / d)) {
  2791. return m * (t -= (2.25 / d)) * t + 0.9375;
  2792. }
  2793. return m * (t -= (2.625 / d)) * t + 0.984375;
  2794. },
  2795. easeInOutBounce: t => (t < 0.5)
  2796. ? effects.easeInBounce(t * 2) * 0.5
  2797. : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5,
  2798. };
  2799. function _pointInLine(p1, p2, t, mode) {
  2800. return {
  2801. x: p1.x + t * (p2.x - p1.x),
  2802. y: p1.y + t * (p2.y - p1.y)
  2803. };
  2804. }
  2805. function _steppedInterpolation(p1, p2, t, mode) {
  2806. return {
  2807. x: p1.x + t * (p2.x - p1.x),
  2808. y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y
  2809. : mode === 'after' ? t < 1 ? p1.y : p2.y
  2810. : t > 0 ? p2.y : p1.y
  2811. };
  2812. }
  2813. function _bezierInterpolation(p1, p2, t, mode) {
  2814. const cp1 = {x: p1.cp2x, y: p1.cp2y};
  2815. const cp2 = {x: p2.cp1x, y: p2.cp1y};
  2816. const a = _pointInLine(p1, cp1, t);
  2817. const b = _pointInLine(cp1, cp2, t);
  2818. const c = _pointInLine(cp2, p2, t);
  2819. const d = _pointInLine(a, b, t);
  2820. const e = _pointInLine(b, c, t);
  2821. return _pointInLine(d, e, t);
  2822. }
  2823. const intlCache = new Map();
  2824. function getNumberFormat(locale, options) {
  2825. options = options || {};
  2826. const cacheKey = locale + JSON.stringify(options);
  2827. let formatter = intlCache.get(cacheKey);
  2828. if (!formatter) {
  2829. formatter = new Intl.NumberFormat(locale, options);
  2830. intlCache.set(cacheKey, formatter);
  2831. }
  2832. return formatter;
  2833. }
  2834. function formatNumber(num, locale, options) {
  2835. return getNumberFormat(locale, options).format(num);
  2836. }
  2837. const getRightToLeftAdapter = function(rectX, width) {
  2838. return {
  2839. x(x) {
  2840. return rectX + rectX + width - x;
  2841. },
  2842. setWidth(w) {
  2843. width = w;
  2844. },
  2845. textAlign(align) {
  2846. if (align === 'center') {
  2847. return align;
  2848. }
  2849. return align === 'right' ? 'left' : 'right';
  2850. },
  2851. xPlus(x, value) {
  2852. return x - value;
  2853. },
  2854. leftForLtr(x, itemWidth) {
  2855. return x - itemWidth;
  2856. },
  2857. };
  2858. };
  2859. const getLeftToRightAdapter = function() {
  2860. return {
  2861. x(x) {
  2862. return x;
  2863. },
  2864. setWidth(w) {
  2865. },
  2866. textAlign(align) {
  2867. return align;
  2868. },
  2869. xPlus(x, value) {
  2870. return x + value;
  2871. },
  2872. leftForLtr(x, _itemWidth) {
  2873. return x;
  2874. },
  2875. };
  2876. };
  2877. function getRtlAdapter(rtl, rectX, width) {
  2878. return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
  2879. }
  2880. function overrideTextDirection(ctx, direction) {
  2881. let style, original;
  2882. if (direction === 'ltr' || direction === 'rtl') {
  2883. style = ctx.canvas.style;
  2884. original = [
  2885. style.getPropertyValue('direction'),
  2886. style.getPropertyPriority('direction'),
  2887. ];
  2888. style.setProperty('direction', direction, 'important');
  2889. ctx.prevTextDirection = original;
  2890. }
  2891. }
  2892. function restoreTextDirection(ctx, original) {
  2893. if (original !== undefined) {
  2894. delete ctx.prevTextDirection;
  2895. ctx.canvas.style.setProperty('direction', original[0], original[1]);
  2896. }
  2897. }
  2898. function propertyFn(property) {
  2899. if (property === 'angle') {
  2900. return {
  2901. between: _angleBetween,
  2902. compare: _angleDiff,
  2903. normalize: _normalizeAngle,
  2904. };
  2905. }
  2906. return {
  2907. between: _isBetween,
  2908. compare: (a, b) => a - b,
  2909. normalize: x => x
  2910. };
  2911. }
  2912. function normalizeSegment({start, end, count, loop, style}) {
  2913. return {
  2914. start: start % count,
  2915. end: end % count,
  2916. loop: loop && (end - start + 1) % count === 0,
  2917. style
  2918. };
  2919. }
  2920. function getSegment(segment, points, bounds) {
  2921. const {property, start: startBound, end: endBound} = bounds;
  2922. const {between, normalize} = propertyFn(property);
  2923. const count = points.length;
  2924. let {start, end, loop} = segment;
  2925. let i, ilen;
  2926. if (loop) {
  2927. start += count;
  2928. end += count;
  2929. for (i = 0, ilen = count; i < ilen; ++i) {
  2930. if (!between(normalize(points[start % count][property]), startBound, endBound)) {
  2931. break;
  2932. }
  2933. start--;
  2934. end--;
  2935. }
  2936. start %= count;
  2937. end %= count;
  2938. }
  2939. if (end < start) {
  2940. end += count;
  2941. }
  2942. return {start, end, loop, style: segment.style};
  2943. }
  2944. function _boundSegment(segment, points, bounds) {
  2945. if (!bounds) {
  2946. return [segment];
  2947. }
  2948. const {property, start: startBound, end: endBound} = bounds;
  2949. const count = points.length;
  2950. const {compare, between, normalize} = propertyFn(property);
  2951. const {start, end, loop, style} = getSegment(segment, points, bounds);
  2952. const result = [];
  2953. let inside = false;
  2954. let subStart = null;
  2955. let value, point, prevValue;
  2956. const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
  2957. const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
  2958. const shouldStart = () => inside || startIsBefore();
  2959. const shouldStop = () => !inside || endIsBefore();
  2960. for (let i = start, prev = start; i <= end; ++i) {
  2961. point = points[i % count];
  2962. if (point.skip) {
  2963. continue;
  2964. }
  2965. value = normalize(point[property]);
  2966. if (value === prevValue) {
  2967. continue;
  2968. }
  2969. inside = between(value, startBound, endBound);
  2970. if (subStart === null && shouldStart()) {
  2971. subStart = compare(value, startBound) === 0 ? i : prev;
  2972. }
  2973. if (subStart !== null && shouldStop()) {
  2974. result.push(normalizeSegment({start: subStart, end: i, loop, count, style}));
  2975. subStart = null;
  2976. }
  2977. prev = i;
  2978. prevValue = value;
  2979. }
  2980. if (subStart !== null) {
  2981. result.push(normalizeSegment({start: subStart, end, loop, count, style}));
  2982. }
  2983. return result;
  2984. }
  2985. function _boundSegments(line, bounds) {
  2986. const result = [];
  2987. const segments = line.segments;
  2988. for (let i = 0; i < segments.length; i++) {
  2989. const sub = _boundSegment(segments[i], line.points, bounds);
  2990. if (sub.length) {
  2991. result.push(...sub);
  2992. }
  2993. }
  2994. return result;
  2995. }
  2996. function findStartAndEnd(points, count, loop, spanGaps) {
  2997. let start = 0;
  2998. let end = count - 1;
  2999. if (loop && !spanGaps) {
  3000. while (start < count && !points[start].skip) {
  3001. start++;
  3002. }
  3003. }
  3004. while (start < count && points[start].skip) {
  3005. start++;
  3006. }
  3007. start %= count;
  3008. if (loop) {
  3009. end += start;
  3010. }
  3011. while (end > start && points[end % count].skip) {
  3012. end--;
  3013. }
  3014. end %= count;
  3015. return {start, end};
  3016. }
  3017. function solidSegments(points, start, max, loop) {
  3018. const count = points.length;
  3019. const result = [];
  3020. let last = start;
  3021. let prev = points[start];
  3022. let end;
  3023. for (end = start + 1; end <= max; ++end) {
  3024. const cur = points[end % count];
  3025. if (cur.skip || cur.stop) {
  3026. if (!prev.skip) {
  3027. loop = false;
  3028. result.push({start: start % count, end: (end - 1) % count, loop});
  3029. start = last = cur.stop ? end : null;
  3030. }
  3031. } else {
  3032. last = end;
  3033. if (prev.skip) {
  3034. start = end;
  3035. }
  3036. }
  3037. prev = cur;
  3038. }
  3039. if (last !== null) {
  3040. result.push({start: start % count, end: last % count, loop});
  3041. }
  3042. return result;
  3043. }
  3044. function _computeSegments(line, segmentOptions) {
  3045. const points = line.points;
  3046. const spanGaps = line.options.spanGaps;
  3047. const count = points.length;
  3048. if (!count) {
  3049. return [];
  3050. }
  3051. const loop = !!line._loop;
  3052. const {start, end} = findStartAndEnd(points, count, loop, spanGaps);
  3053. if (spanGaps === true) {
  3054. return splitByStyles(line, [{start, end, loop}], points, segmentOptions);
  3055. }
  3056. const max = end < start ? end + count : end;
  3057. const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
  3058. return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
  3059. }
  3060. function splitByStyles(line, segments, points, segmentOptions) {
  3061. if (!segmentOptions || !segmentOptions.setContext || !points) {
  3062. return segments;
  3063. }
  3064. return doSplitByStyles(line, segments, points, segmentOptions);
  3065. }
  3066. function doSplitByStyles(line, segments, points, segmentOptions) {
  3067. const chartContext = line._chart.getContext();
  3068. const baseStyle = readStyle(line.options);
  3069. const {_datasetIndex: datasetIndex, options: {spanGaps}} = line;
  3070. const count = points.length;
  3071. const result = [];
  3072. let prevStyle = baseStyle;
  3073. let start = segments[0].start;
  3074. let i = start;
  3075. function addStyle(s, e, l, st) {
  3076. const dir = spanGaps ? -1 : 1;
  3077. if (s === e) {
  3078. return;
  3079. }
  3080. s += count;
  3081. while (points[s % count].skip) {
  3082. s -= dir;
  3083. }
  3084. while (points[e % count].skip) {
  3085. e += dir;
  3086. }
  3087. if (s % count !== e % count) {
  3088. result.push({start: s % count, end: e % count, loop: l, style: st});
  3089. prevStyle = st;
  3090. start = e % count;
  3091. }
  3092. }
  3093. for (const segment of segments) {
  3094. start = spanGaps ? start : segment.start;
  3095. let prev = points[start % count];
  3096. let style;
  3097. for (i = start + 1; i <= segment.end; i++) {
  3098. const pt = points[i % count];
  3099. style = readStyle(segmentOptions.setContext(createContext(chartContext, {
  3100. type: 'segment',
  3101. p0: prev,
  3102. p1: pt,
  3103. p0DataIndex: (i - 1) % count,
  3104. p1DataIndex: i % count,
  3105. datasetIndex
  3106. })));
  3107. if (styleChanged(style, prevStyle)) {
  3108. addStyle(start, i - 1, segment.loop, prevStyle);
  3109. }
  3110. prev = pt;
  3111. prevStyle = style;
  3112. }
  3113. if (start < i - 1) {
  3114. addStyle(start, i - 1, segment.loop, prevStyle);
  3115. }
  3116. }
  3117. return result;
  3118. }
  3119. function readStyle(options) {
  3120. return {
  3121. backgroundColor: options.backgroundColor,
  3122. borderCapStyle: options.borderCapStyle,
  3123. borderDash: options.borderDash,
  3124. borderDashOffset: options.borderDashOffset,
  3125. borderJoinStyle: options.borderJoinStyle,
  3126. borderWidth: options.borderWidth,
  3127. borderColor: options.borderColor
  3128. };
  3129. }
  3130. function styleChanged(style, prevStyle) {
  3131. return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle);
  3132. }
  3133. var helpers = /*#__PURE__*/Object.freeze({
  3134. __proto__: null,
  3135. easingEffects: effects,
  3136. color: color,
  3137. getHoverColor: getHoverColor,
  3138. noop: noop,
  3139. uid: uid,
  3140. isNullOrUndef: isNullOrUndef,
  3141. isArray: isArray,
  3142. isObject: isObject,
  3143. isFinite: isNumberFinite,
  3144. finiteOrDefault: finiteOrDefault,
  3145. valueOrDefault: valueOrDefault,
  3146. toPercentage: toPercentage,
  3147. toDimension: toDimension,
  3148. callback: callback,
  3149. each: each,
  3150. _elementsEqual: _elementsEqual,
  3151. clone: clone,
  3152. _merger: _merger,
  3153. merge: merge,
  3154. mergeIf: mergeIf,
  3155. _mergerIf: _mergerIf,
  3156. _deprecated: _deprecated,
  3157. resolveObjectKey: resolveObjectKey,
  3158. _capitalize: _capitalize,
  3159. defined: defined,
  3160. isFunction: isFunction,
  3161. setsEqual: setsEqual,
  3162. _isClickEvent: _isClickEvent,
  3163. toFontString: toFontString,
  3164. _measureText: _measureText,
  3165. _longestText: _longestText,
  3166. _alignPixel: _alignPixel,
  3167. clearCanvas: clearCanvas,
  3168. drawPoint: drawPoint,
  3169. _isPointInArea: _isPointInArea,
  3170. clipArea: clipArea,
  3171. unclipArea: unclipArea,
  3172. _steppedLineTo: _steppedLineTo,
  3173. _bezierCurveTo: _bezierCurveTo,
  3174. renderText: renderText,
  3175. addRoundedRectPath: addRoundedRectPath,
  3176. _lookup: _lookup,
  3177. _lookupByKey: _lookupByKey,
  3178. _rlookupByKey: _rlookupByKey,
  3179. _filterBetween: _filterBetween,
  3180. listenArrayEvents: listenArrayEvents,
  3181. unlistenArrayEvents: unlistenArrayEvents,
  3182. _arrayUnique: _arrayUnique,
  3183. _createResolver: _createResolver,
  3184. _attachContext: _attachContext,
  3185. _descriptors: _descriptors,
  3186. splineCurve: splineCurve,
  3187. splineCurveMonotone: splineCurveMonotone,
  3188. _updateBezierControlPoints: _updateBezierControlPoints,
  3189. _isDomSupported: _isDomSupported,
  3190. _getParentNode: _getParentNode,
  3191. getStyle: getStyle,
  3192. getRelativePosition: getRelativePosition$1,
  3193. getMaximumSize: getMaximumSize,
  3194. retinaScale: retinaScale,
  3195. supportsEventListenerOptions: supportsEventListenerOptions,
  3196. readUsedSize: readUsedSize,
  3197. fontString: fontString,
  3198. requestAnimFrame: requestAnimFrame,
  3199. throttled: throttled,
  3200. debounce: debounce,
  3201. _toLeftRightCenter: _toLeftRightCenter,
  3202. _alignStartEnd: _alignStartEnd,
  3203. _textX: _textX,
  3204. _pointInLine: _pointInLine,
  3205. _steppedInterpolation: _steppedInterpolation,
  3206. _bezierInterpolation: _bezierInterpolation,
  3207. formatNumber: formatNumber,
  3208. toLineHeight: toLineHeight,
  3209. _readValueToProps: _readValueToProps,
  3210. toTRBL: toTRBL,
  3211. toTRBLCorners: toTRBLCorners,
  3212. toPadding: toPadding,
  3213. toFont: toFont,
  3214. resolve: resolve,
  3215. _addGrace: _addGrace,
  3216. createContext: createContext,
  3217. PI: PI,
  3218. TAU: TAU,
  3219. PITAU: PITAU,
  3220. INFINITY: INFINITY,
  3221. RAD_PER_DEG: RAD_PER_DEG,
  3222. HALF_PI: HALF_PI,
  3223. QUARTER_PI: QUARTER_PI,
  3224. TWO_THIRDS_PI: TWO_THIRDS_PI,
  3225. log10: log10,
  3226. sign: sign,
  3227. niceNum: niceNum,
  3228. _factorize: _factorize,
  3229. isNumber: isNumber,
  3230. almostEquals: almostEquals,
  3231. almostWhole: almostWhole,
  3232. _setMinAndMaxByKey: _setMinAndMaxByKey,
  3233. toRadians: toRadians,
  3234. toDegrees: toDegrees,
  3235. _decimalPlaces: _decimalPlaces,
  3236. getAngleFromPoint: getAngleFromPoint,
  3237. distanceBetweenPoints: distanceBetweenPoints,
  3238. _angleDiff: _angleDiff,
  3239. _normalizeAngle: _normalizeAngle,
  3240. _angleBetween: _angleBetween,
  3241. _limitValue: _limitValue,
  3242. _int16Range: _int16Range,
  3243. _isBetween: _isBetween,
  3244. getRtlAdapter: getRtlAdapter,
  3245. overrideTextDirection: overrideTextDirection,
  3246. restoreTextDirection: restoreTextDirection,
  3247. _boundSegment: _boundSegment,
  3248. _boundSegments: _boundSegments,
  3249. _computeSegments: _computeSegments
  3250. });
  3251. class BasePlatform {
  3252. acquireContext(canvas, aspectRatio) {}
  3253. releaseContext(context) {
  3254. return false;
  3255. }
  3256. addEventListener(chart, type, listener) {}
  3257. removeEventListener(chart, type, listener) {}
  3258. getDevicePixelRatio() {
  3259. return 1;
  3260. }
  3261. getMaximumSize(element, width, height, aspectRatio) {
  3262. width = Math.max(0, width || element.width);
  3263. height = height || element.height;
  3264. return {
  3265. width,
  3266. height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
  3267. };
  3268. }
  3269. isAttached(canvas) {
  3270. return true;
  3271. }
  3272. updateConfig(config) {
  3273. }
  3274. }
  3275. class BasicPlatform extends BasePlatform {
  3276. acquireContext(item) {
  3277. return item && item.getContext && item.getContext('2d') || null;
  3278. }
  3279. updateConfig(config) {
  3280. config.options.animation = false;
  3281. }
  3282. }
  3283. const EXPANDO_KEY = '$chartjs';
  3284. const EVENT_TYPES = {
  3285. touchstart: 'mousedown',
  3286. touchmove: 'mousemove',
  3287. touchend: 'mouseup',
  3288. pointerenter: 'mouseenter',
  3289. pointerdown: 'mousedown',
  3290. pointermove: 'mousemove',
  3291. pointerup: 'mouseup',
  3292. pointerleave: 'mouseout',
  3293. pointerout: 'mouseout'
  3294. };
  3295. const isNullOrEmpty = value => value === null || value === '';
  3296. function initCanvas(canvas, aspectRatio) {
  3297. const style = canvas.style;
  3298. const renderHeight = canvas.getAttribute('height');
  3299. const renderWidth = canvas.getAttribute('width');
  3300. canvas[EXPANDO_KEY] = {
  3301. initial: {
  3302. height: renderHeight,
  3303. width: renderWidth,
  3304. style: {
  3305. display: style.display,
  3306. height: style.height,
  3307. width: style.width
  3308. }
  3309. }
  3310. };
  3311. style.display = style.display || 'block';
  3312. style.boxSizing = style.boxSizing || 'border-box';
  3313. if (isNullOrEmpty(renderWidth)) {
  3314. const displayWidth = readUsedSize(canvas, 'width');
  3315. if (displayWidth !== undefined) {
  3316. canvas.width = displayWidth;
  3317. }
  3318. }
  3319. if (isNullOrEmpty(renderHeight)) {
  3320. if (canvas.style.height === '') {
  3321. canvas.height = canvas.width / (aspectRatio || 2);
  3322. } else {
  3323. const displayHeight = readUsedSize(canvas, 'height');
  3324. if (displayHeight !== undefined) {
  3325. canvas.height = displayHeight;
  3326. }
  3327. }
  3328. }
  3329. return canvas;
  3330. }
  3331. const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
  3332. function addListener(node, type, listener) {
  3333. node.addEventListener(type, listener, eventListenerOptions);
  3334. }
  3335. function removeListener(chart, type, listener) {
  3336. chart.canvas.removeEventListener(type, listener, eventListenerOptions);
  3337. }
  3338. function fromNativeEvent(event, chart) {
  3339. const type = EVENT_TYPES[event.type] || event.type;
  3340. const {x, y} = getRelativePosition$1(event, chart);
  3341. return {
  3342. type,
  3343. chart,
  3344. native: event,
  3345. x: x !== undefined ? x : null,
  3346. y: y !== undefined ? y : null,
  3347. };
  3348. }
  3349. function nodeListContains(nodeList, canvas) {
  3350. for (const node of nodeList) {
  3351. if (node === canvas || node.contains(canvas)) {
  3352. return true;
  3353. }
  3354. }
  3355. }
  3356. function createAttachObserver(chart, type, listener) {
  3357. const canvas = chart.canvas;
  3358. const observer = new MutationObserver(entries => {
  3359. let trigger = false;
  3360. for (const entry of entries) {
  3361. trigger = trigger || nodeListContains(entry.addedNodes, canvas);
  3362. trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
  3363. }
  3364. if (trigger) {
  3365. listener();
  3366. }
  3367. });
  3368. observer.observe(document, {childList: true, subtree: true});
  3369. return observer;
  3370. }
  3371. function createDetachObserver(chart, type, listener) {
  3372. const canvas = chart.canvas;
  3373. const observer = new MutationObserver(entries => {
  3374. let trigger = false;
  3375. for (const entry of entries) {
  3376. trigger = trigger || nodeListContains(entry.removedNodes, canvas);
  3377. trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
  3378. }
  3379. if (trigger) {
  3380. listener();
  3381. }
  3382. });
  3383. observer.observe(document, {childList: true, subtree: true});
  3384. return observer;
  3385. }
  3386. const drpListeningCharts = new Map();
  3387. let oldDevicePixelRatio = 0;
  3388. function onWindowResize() {
  3389. const dpr = window.devicePixelRatio;
  3390. if (dpr === oldDevicePixelRatio) {
  3391. return;
  3392. }
  3393. oldDevicePixelRatio = dpr;
  3394. drpListeningCharts.forEach((resize, chart) => {
  3395. if (chart.currentDevicePixelRatio !== dpr) {
  3396. resize();
  3397. }
  3398. });
  3399. }
  3400. function listenDevicePixelRatioChanges(chart, resize) {
  3401. if (!drpListeningCharts.size) {
  3402. window.addEventListener('resize', onWindowResize);
  3403. }
  3404. drpListeningCharts.set(chart, resize);
  3405. }
  3406. function unlistenDevicePixelRatioChanges(chart) {
  3407. drpListeningCharts.delete(chart);
  3408. if (!drpListeningCharts.size) {
  3409. window.removeEventListener('resize', onWindowResize);
  3410. }
  3411. }
  3412. function createResizeObserver(chart, type, listener) {
  3413. const canvas = chart.canvas;
  3414. const container = canvas && _getParentNode(canvas);
  3415. if (!container) {
  3416. return;
  3417. }
  3418. const resize = throttled((width, height) => {
  3419. const w = container.clientWidth;
  3420. listener(width, height);
  3421. if (w < container.clientWidth) {
  3422. listener();
  3423. }
  3424. }, window);
  3425. const observer = new ResizeObserver(entries => {
  3426. const entry = entries[0];
  3427. const width = entry.contentRect.width;
  3428. const height = entry.contentRect.height;
  3429. if (width === 0 && height === 0) {
  3430. return;
  3431. }
  3432. resize(width, height);
  3433. });
  3434. observer.observe(container);
  3435. listenDevicePixelRatioChanges(chart, resize);
  3436. return observer;
  3437. }
  3438. function releaseObserver(chart, type, observer) {
  3439. if (observer) {
  3440. observer.disconnect();
  3441. }
  3442. if (type === 'resize') {
  3443. unlistenDevicePixelRatioChanges(chart);
  3444. }
  3445. }
  3446. function createProxyAndListen(chart, type, listener) {
  3447. const canvas = chart.canvas;
  3448. const proxy = throttled((event) => {
  3449. if (chart.ctx !== null) {
  3450. listener(fromNativeEvent(event, chart));
  3451. }
  3452. }, chart, (args) => {
  3453. const event = args[0];
  3454. return [event, event.offsetX, event.offsetY];
  3455. });
  3456. addListener(canvas, type, proxy);
  3457. return proxy;
  3458. }
  3459. class DomPlatform extends BasePlatform {
  3460. acquireContext(canvas, aspectRatio) {
  3461. const context = canvas && canvas.getContext && canvas.getContext('2d');
  3462. if (context && context.canvas === canvas) {
  3463. initCanvas(canvas, aspectRatio);
  3464. return context;
  3465. }
  3466. return null;
  3467. }
  3468. releaseContext(context) {
  3469. const canvas = context.canvas;
  3470. if (!canvas[EXPANDO_KEY]) {
  3471. return false;
  3472. }
  3473. const initial = canvas[EXPANDO_KEY].initial;
  3474. ['height', 'width'].forEach((prop) => {
  3475. const value = initial[prop];
  3476. if (isNullOrUndef(value)) {
  3477. canvas.removeAttribute(prop);
  3478. } else {
  3479. canvas.setAttribute(prop, value);
  3480. }
  3481. });
  3482. const style = initial.style || {};
  3483. Object.keys(style).forEach((key) => {
  3484. canvas.style[key] = style[key];
  3485. });
  3486. canvas.width = canvas.width;
  3487. delete canvas[EXPANDO_KEY];
  3488. return true;
  3489. }
  3490. addEventListener(chart, type, listener) {
  3491. this.removeEventListener(chart, type);
  3492. const proxies = chart.$proxies || (chart.$proxies = {});
  3493. const handlers = {
  3494. attach: createAttachObserver,
  3495. detach: createDetachObserver,
  3496. resize: createResizeObserver
  3497. };
  3498. const handler = handlers[type] || createProxyAndListen;
  3499. proxies[type] = handler(chart, type, listener);
  3500. }
  3501. removeEventListener(chart, type) {
  3502. const proxies = chart.$proxies || (chart.$proxies = {});
  3503. const proxy = proxies[type];
  3504. if (!proxy) {
  3505. return;
  3506. }
  3507. const handlers = {
  3508. attach: releaseObserver,
  3509. detach: releaseObserver,
  3510. resize: releaseObserver
  3511. };
  3512. const handler = handlers[type] || removeListener;
  3513. handler(chart, type, proxy);
  3514. proxies[type] = undefined;
  3515. }
  3516. getDevicePixelRatio() {
  3517. return window.devicePixelRatio;
  3518. }
  3519. getMaximumSize(canvas, width, height, aspectRatio) {
  3520. return getMaximumSize(canvas, width, height, aspectRatio);
  3521. }
  3522. isAttached(canvas) {
  3523. const container = _getParentNode(canvas);
  3524. return !!(container && container.isConnected);
  3525. }
  3526. }
  3527. function _detectPlatform(canvas) {
  3528. if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {
  3529. return BasicPlatform;
  3530. }
  3531. return DomPlatform;
  3532. }
  3533. var platforms = /*#__PURE__*/Object.freeze({
  3534. __proto__: null,
  3535. _detectPlatform: _detectPlatform,
  3536. BasePlatform: BasePlatform,
  3537. BasicPlatform: BasicPlatform,
  3538. DomPlatform: DomPlatform
  3539. });
  3540. const transparent = 'transparent';
  3541. const interpolators = {
  3542. boolean(from, to, factor) {
  3543. return factor > 0.5 ? to : from;
  3544. },
  3545. color(from, to, factor) {
  3546. const c0 = color(from || transparent);
  3547. const c1 = c0.valid && color(to || transparent);
  3548. return c1 && c1.valid
  3549. ? c1.mix(c0, factor).hexString()
  3550. : to;
  3551. },
  3552. number(from, to, factor) {
  3553. return from + (to - from) * factor;
  3554. }
  3555. };
  3556. class Animation {
  3557. constructor(cfg, target, prop, to) {
  3558. const currentValue = target[prop];
  3559. to = resolve([cfg.to, to, currentValue, cfg.from]);
  3560. const from = resolve([cfg.from, currentValue, to]);
  3561. this._active = true;
  3562. this._fn = cfg.fn || interpolators[cfg.type || typeof from];
  3563. this._easing = effects[cfg.easing] || effects.linear;
  3564. this._start = Math.floor(Date.now() + (cfg.delay || 0));
  3565. this._duration = this._total = Math.floor(cfg.duration);
  3566. this._loop = !!cfg.loop;
  3567. this._target = target;
  3568. this._prop = prop;
  3569. this._from = from;
  3570. this._to = to;
  3571. this._promises = undefined;
  3572. }
  3573. active() {
  3574. return this._active;
  3575. }
  3576. update(cfg, to, date) {
  3577. if (this._active) {
  3578. this._notify(false);
  3579. const currentValue = this._target[this._prop];
  3580. const elapsed = date - this._start;
  3581. const remain = this._duration - elapsed;
  3582. this._start = date;
  3583. this._duration = Math.floor(Math.max(remain, cfg.duration));
  3584. this._total += elapsed;
  3585. this._loop = !!cfg.loop;
  3586. this._to = resolve([cfg.to, to, currentValue, cfg.from]);
  3587. this._from = resolve([cfg.from, currentValue, to]);
  3588. }
  3589. }
  3590. cancel() {
  3591. if (this._active) {
  3592. this.tick(Date.now());
  3593. this._active = false;
  3594. this._notify(false);
  3595. }
  3596. }
  3597. tick(date) {
  3598. const elapsed = date - this._start;
  3599. const duration = this._duration;
  3600. const prop = this._prop;
  3601. const from = this._from;
  3602. const loop = this._loop;
  3603. const to = this._to;
  3604. let factor;
  3605. this._active = from !== to && (loop || (elapsed < duration));
  3606. if (!this._active) {
  3607. this._target[prop] = to;
  3608. this._notify(true);
  3609. return;
  3610. }
  3611. if (elapsed < 0) {
  3612. this._target[prop] = from;
  3613. return;
  3614. }
  3615. factor = (elapsed / duration) % 2;
  3616. factor = loop && factor > 1 ? 2 - factor : factor;
  3617. factor = this._easing(Math.min(1, Math.max(0, factor)));
  3618. this._target[prop] = this._fn(from, to, factor);
  3619. }
  3620. wait() {
  3621. const promises = this._promises || (this._promises = []);
  3622. return new Promise((res, rej) => {
  3623. promises.push({res, rej});
  3624. });
  3625. }
  3626. _notify(resolved) {
  3627. const method = resolved ? 'res' : 'rej';
  3628. const promises = this._promises || [];
  3629. for (let i = 0; i < promises.length; i++) {
  3630. promises[i][method]();
  3631. }
  3632. }
  3633. }
  3634. const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
  3635. const colors = ['color', 'borderColor', 'backgroundColor'];
  3636. defaults.set('animation', {
  3637. delay: undefined,
  3638. duration: 1000,
  3639. easing: 'easeOutQuart',
  3640. fn: undefined,
  3641. from: undefined,
  3642. loop: undefined,
  3643. to: undefined,
  3644. type: undefined,
  3645. });
  3646. const animationOptions = Object.keys(defaults.animation);
  3647. defaults.describe('animation', {
  3648. _fallback: false,
  3649. _indexable: false,
  3650. _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
  3651. });
  3652. defaults.set('animations', {
  3653. colors: {
  3654. type: 'color',
  3655. properties: colors
  3656. },
  3657. numbers: {
  3658. type: 'number',
  3659. properties: numbers
  3660. },
  3661. });
  3662. defaults.describe('animations', {
  3663. _fallback: 'animation',
  3664. });
  3665. defaults.set('transitions', {
  3666. active: {
  3667. animation: {
  3668. duration: 400
  3669. }
  3670. },
  3671. resize: {
  3672. animation: {
  3673. duration: 0
  3674. }
  3675. },
  3676. show: {
  3677. animations: {
  3678. colors: {
  3679. from: 'transparent'
  3680. },
  3681. visible: {
  3682. type: 'boolean',
  3683. duration: 0
  3684. },
  3685. }
  3686. },
  3687. hide: {
  3688. animations: {
  3689. colors: {
  3690. to: 'transparent'
  3691. },
  3692. visible: {
  3693. type: 'boolean',
  3694. easing: 'linear',
  3695. fn: v => v | 0
  3696. },
  3697. }
  3698. }
  3699. });
  3700. class Animations {
  3701. constructor(chart, config) {
  3702. this._chart = chart;
  3703. this._properties = new Map();
  3704. this.configure(config);
  3705. }
  3706. configure(config) {
  3707. if (!isObject(config)) {
  3708. return;
  3709. }
  3710. const animatedProps = this._properties;
  3711. Object.getOwnPropertyNames(config).forEach(key => {
  3712. const cfg = config[key];
  3713. if (!isObject(cfg)) {
  3714. return;
  3715. }
  3716. const resolved = {};
  3717. for (const option of animationOptions) {
  3718. resolved[option] = cfg[option];
  3719. }
  3720. (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {
  3721. if (prop === key || !animatedProps.has(prop)) {
  3722. animatedProps.set(prop, resolved);
  3723. }
  3724. });
  3725. });
  3726. }
  3727. _animateOptions(target, values) {
  3728. const newOptions = values.options;
  3729. const options = resolveTargetOptions(target, newOptions);
  3730. if (!options) {
  3731. return [];
  3732. }
  3733. const animations = this._createAnimations(options, newOptions);
  3734. if (newOptions.$shared) {
  3735. awaitAll(target.options.$animations, newOptions).then(() => {
  3736. target.options = newOptions;
  3737. }, () => {
  3738. });
  3739. }
  3740. return animations;
  3741. }
  3742. _createAnimations(target, values) {
  3743. const animatedProps = this._properties;
  3744. const animations = [];
  3745. const running = target.$animations || (target.$animations = {});
  3746. const props = Object.keys(values);
  3747. const date = Date.now();
  3748. let i;
  3749. for (i = props.length - 1; i >= 0; --i) {
  3750. const prop = props[i];
  3751. if (prop.charAt(0) === '$') {
  3752. continue;
  3753. }
  3754. if (prop === 'options') {
  3755. animations.push(...this._animateOptions(target, values));
  3756. continue;
  3757. }
  3758. const value = values[prop];
  3759. let animation = running[prop];
  3760. const cfg = animatedProps.get(prop);
  3761. if (animation) {
  3762. if (cfg && animation.active()) {
  3763. animation.update(cfg, value, date);
  3764. continue;
  3765. } else {
  3766. animation.cancel();
  3767. }
  3768. }
  3769. if (!cfg || !cfg.duration) {
  3770. target[prop] = value;
  3771. continue;
  3772. }
  3773. running[prop] = animation = new Animation(cfg, target, prop, value);
  3774. animations.push(animation);
  3775. }
  3776. return animations;
  3777. }
  3778. update(target, values) {
  3779. if (this._properties.size === 0) {
  3780. Object.assign(target, values);
  3781. return;
  3782. }
  3783. const animations = this._createAnimations(target, values);
  3784. if (animations.length) {
  3785. animator.add(this._chart, animations);
  3786. return true;
  3787. }
  3788. }
  3789. }
  3790. function awaitAll(animations, properties) {
  3791. const running = [];
  3792. const keys = Object.keys(properties);
  3793. for (let i = 0; i < keys.length; i++) {
  3794. const anim = animations[keys[i]];
  3795. if (anim && anim.active()) {
  3796. running.push(anim.wait());
  3797. }
  3798. }
  3799. return Promise.all(running);
  3800. }
  3801. function resolveTargetOptions(target, newOptions) {
  3802. if (!newOptions) {
  3803. return;
  3804. }
  3805. let options = target.options;
  3806. if (!options) {
  3807. target.options = newOptions;
  3808. return;
  3809. }
  3810. if (options.$shared) {
  3811. target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
  3812. }
  3813. return options;
  3814. }
  3815. function scaleClip(scale, allowedOverflow) {
  3816. const opts = scale && scale.options || {};
  3817. const reverse = opts.reverse;
  3818. const min = opts.min === undefined ? allowedOverflow : 0;
  3819. const max = opts.max === undefined ? allowedOverflow : 0;
  3820. return {
  3821. start: reverse ? max : min,
  3822. end: reverse ? min : max
  3823. };
  3824. }
  3825. function defaultClip(xScale, yScale, allowedOverflow) {
  3826. if (allowedOverflow === false) {
  3827. return false;
  3828. }
  3829. const x = scaleClip(xScale, allowedOverflow);
  3830. const y = scaleClip(yScale, allowedOverflow);
  3831. return {
  3832. top: y.end,
  3833. right: x.end,
  3834. bottom: y.start,
  3835. left: x.start
  3836. };
  3837. }
  3838. function toClip(value) {
  3839. let t, r, b, l;
  3840. if (isObject(value)) {
  3841. t = value.top;
  3842. r = value.right;
  3843. b = value.bottom;
  3844. l = value.left;
  3845. } else {
  3846. t = r = b = l = value;
  3847. }
  3848. return {
  3849. top: t,
  3850. right: r,
  3851. bottom: b,
  3852. left: l,
  3853. disabled: value === false
  3854. };
  3855. }
  3856. function getSortedDatasetIndices(chart, filterVisible) {
  3857. const keys = [];
  3858. const metasets = chart._getSortedDatasetMetas(filterVisible);
  3859. let i, ilen;
  3860. for (i = 0, ilen = metasets.length; i < ilen; ++i) {
  3861. keys.push(metasets[i].index);
  3862. }
  3863. return keys;
  3864. }
  3865. function applyStack(stack, value, dsIndex, options = {}) {
  3866. const keys = stack.keys;
  3867. const singleMode = options.mode === 'single';
  3868. let i, ilen, datasetIndex, otherValue;
  3869. if (value === null) {
  3870. return;
  3871. }
  3872. for (i = 0, ilen = keys.length; i < ilen; ++i) {
  3873. datasetIndex = +keys[i];
  3874. if (datasetIndex === dsIndex) {
  3875. if (options.all) {
  3876. continue;
  3877. }
  3878. break;
  3879. }
  3880. otherValue = stack.values[datasetIndex];
  3881. if (isNumberFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {
  3882. value += otherValue;
  3883. }
  3884. }
  3885. return value;
  3886. }
  3887. function convertObjectDataToArray(data) {
  3888. const keys = Object.keys(data);
  3889. const adata = new Array(keys.length);
  3890. let i, ilen, key;
  3891. for (i = 0, ilen = keys.length; i < ilen; ++i) {
  3892. key = keys[i];
  3893. adata[i] = {
  3894. x: key,
  3895. y: data[key]
  3896. };
  3897. }
  3898. return adata;
  3899. }
  3900. function isStacked(scale, meta) {
  3901. const stacked = scale && scale.options.stacked;
  3902. return stacked || (stacked === undefined && meta.stack !== undefined);
  3903. }
  3904. function getStackKey(indexScale, valueScale, meta) {
  3905. return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
  3906. }
  3907. function getUserBounds(scale) {
  3908. const {min, max, minDefined, maxDefined} = scale.getUserBounds();
  3909. return {
  3910. min: minDefined ? min : Number.NEGATIVE_INFINITY,
  3911. max: maxDefined ? max : Number.POSITIVE_INFINITY
  3912. };
  3913. }
  3914. function getOrCreateStack(stacks, stackKey, indexValue) {
  3915. const subStack = stacks[stackKey] || (stacks[stackKey] = {});
  3916. return subStack[indexValue] || (subStack[indexValue] = {});
  3917. }
  3918. function getLastIndexInStack(stack, vScale, positive, type) {
  3919. for (const meta of vScale.getMatchingVisibleMetas(type).reverse()) {
  3920. const value = stack[meta.index];
  3921. if ((positive && value > 0) || (!positive && value < 0)) {
  3922. return meta.index;
  3923. }
  3924. }
  3925. return null;
  3926. }
  3927. function updateStacks(controller, parsed) {
  3928. const {chart, _cachedMeta: meta} = controller;
  3929. const stacks = chart._stacks || (chart._stacks = {});
  3930. const {iScale, vScale, index: datasetIndex} = meta;
  3931. const iAxis = iScale.axis;
  3932. const vAxis = vScale.axis;
  3933. const key = getStackKey(iScale, vScale, meta);
  3934. const ilen = parsed.length;
  3935. let stack;
  3936. for (let i = 0; i < ilen; ++i) {
  3937. const item = parsed[i];
  3938. const {[iAxis]: index, [vAxis]: value} = item;
  3939. const itemStacks = item._stacks || (item._stacks = {});
  3940. stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
  3941. stack[datasetIndex] = value;
  3942. stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
  3943. stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
  3944. }
  3945. }
  3946. function getFirstScaleId(chart, axis) {
  3947. const scales = chart.scales;
  3948. return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
  3949. }
  3950. function createDatasetContext(parent, index) {
  3951. return createContext(parent,
  3952. {
  3953. active: false,
  3954. dataset: undefined,
  3955. datasetIndex: index,
  3956. index,
  3957. mode: 'default',
  3958. type: 'dataset'
  3959. }
  3960. );
  3961. }
  3962. function createDataContext(parent, index, element) {
  3963. return createContext(parent, {
  3964. active: false,
  3965. dataIndex: index,
  3966. parsed: undefined,
  3967. raw: undefined,
  3968. element,
  3969. index,
  3970. mode: 'default',
  3971. type: 'data'
  3972. });
  3973. }
  3974. function clearStacks(meta, items) {
  3975. const datasetIndex = meta.controller.index;
  3976. const axis = meta.vScale && meta.vScale.axis;
  3977. if (!axis) {
  3978. return;
  3979. }
  3980. items = items || meta._parsed;
  3981. for (const parsed of items) {
  3982. const stacks = parsed._stacks;
  3983. if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
  3984. return;
  3985. }
  3986. delete stacks[axis][datasetIndex];
  3987. }
  3988. }
  3989. const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
  3990. const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);
  3991. const createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked
  3992. && {keys: getSortedDatasetIndices(chart, true), values: null};
  3993. class DatasetController {
  3994. constructor(chart, datasetIndex) {
  3995. this.chart = chart;
  3996. this._ctx = chart.ctx;
  3997. this.index = datasetIndex;
  3998. this._cachedDataOpts = {};
  3999. this._cachedMeta = this.getMeta();
  4000. this._type = this._cachedMeta.type;
  4001. this.options = undefined;
  4002. this._parsing = false;
  4003. this._data = undefined;
  4004. this._objectData = undefined;
  4005. this._sharedOptions = undefined;
  4006. this._drawStart = undefined;
  4007. this._drawCount = undefined;
  4008. this.enableOptionSharing = false;
  4009. this.$context = undefined;
  4010. this._syncList = [];
  4011. this.initialize();
  4012. }
  4013. initialize() {
  4014. const meta = this._cachedMeta;
  4015. this.configure();
  4016. this.linkScales();
  4017. meta._stacked = isStacked(meta.vScale, meta);
  4018. this.addElements();
  4019. }
  4020. updateIndex(datasetIndex) {
  4021. if (this.index !== datasetIndex) {
  4022. clearStacks(this._cachedMeta);
  4023. }
  4024. this.index = datasetIndex;
  4025. }
  4026. linkScales() {
  4027. const chart = this.chart;
  4028. const meta = this._cachedMeta;
  4029. const dataset = this.getDataset();
  4030. const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y;
  4031. const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
  4032. const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
  4033. const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
  4034. const indexAxis = meta.indexAxis;
  4035. const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
  4036. const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
  4037. meta.xScale = this.getScaleForId(xid);
  4038. meta.yScale = this.getScaleForId(yid);
  4039. meta.rScale = this.getScaleForId(rid);
  4040. meta.iScale = this.getScaleForId(iid);
  4041. meta.vScale = this.getScaleForId(vid);
  4042. }
  4043. getDataset() {
  4044. return this.chart.data.datasets[this.index];
  4045. }
  4046. getMeta() {
  4047. return this.chart.getDatasetMeta(this.index);
  4048. }
  4049. getScaleForId(scaleID) {
  4050. return this.chart.scales[scaleID];
  4051. }
  4052. _getOtherScale(scale) {
  4053. const meta = this._cachedMeta;
  4054. return scale === meta.iScale
  4055. ? meta.vScale
  4056. : meta.iScale;
  4057. }
  4058. reset() {
  4059. this._update('reset');
  4060. }
  4061. _destroy() {
  4062. const meta = this._cachedMeta;
  4063. if (this._data) {
  4064. unlistenArrayEvents(this._data, this);
  4065. }
  4066. if (meta._stacked) {
  4067. clearStacks(meta);
  4068. }
  4069. }
  4070. _dataCheck() {
  4071. const dataset = this.getDataset();
  4072. const data = dataset.data || (dataset.data = []);
  4073. const _data = this._data;
  4074. if (isObject(data)) {
  4075. this._data = convertObjectDataToArray(data);
  4076. } else if (_data !== data) {
  4077. if (_data) {
  4078. unlistenArrayEvents(_data, this);
  4079. const meta = this._cachedMeta;
  4080. clearStacks(meta);
  4081. meta._parsed = [];
  4082. }
  4083. if (data && Object.isExtensible(data)) {
  4084. listenArrayEvents(data, this);
  4085. }
  4086. this._syncList = [];
  4087. this._data = data;
  4088. }
  4089. }
  4090. addElements() {
  4091. const meta = this._cachedMeta;
  4092. this._dataCheck();
  4093. if (this.datasetElementType) {
  4094. meta.dataset = new this.datasetElementType();
  4095. }
  4096. }
  4097. buildOrUpdateElements(resetNewElements) {
  4098. const meta = this._cachedMeta;
  4099. const dataset = this.getDataset();
  4100. let stackChanged = false;
  4101. this._dataCheck();
  4102. const oldStacked = meta._stacked;
  4103. meta._stacked = isStacked(meta.vScale, meta);
  4104. if (meta.stack !== dataset.stack) {
  4105. stackChanged = true;
  4106. clearStacks(meta);
  4107. meta.stack = dataset.stack;
  4108. }
  4109. this._resyncElements(resetNewElements);
  4110. if (stackChanged || oldStacked !== meta._stacked) {
  4111. updateStacks(this, meta._parsed);
  4112. }
  4113. }
  4114. configure() {
  4115. const config = this.chart.config;
  4116. const scopeKeys = config.datasetScopeKeys(this._type);
  4117. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
  4118. this.options = config.createResolver(scopes, this.getContext());
  4119. this._parsing = this.options.parsing;
  4120. this._cachedDataOpts = {};
  4121. }
  4122. parse(start, count) {
  4123. const {_cachedMeta: meta, _data: data} = this;
  4124. const {iScale, _stacked} = meta;
  4125. const iAxis = iScale.axis;
  4126. let sorted = start === 0 && count === data.length ? true : meta._sorted;
  4127. let prev = start > 0 && meta._parsed[start - 1];
  4128. let i, cur, parsed;
  4129. if (this._parsing === false) {
  4130. meta._parsed = data;
  4131. meta._sorted = true;
  4132. parsed = data;
  4133. } else {
  4134. if (isArray(data[start])) {
  4135. parsed = this.parseArrayData(meta, data, start, count);
  4136. } else if (isObject(data[start])) {
  4137. parsed = this.parseObjectData(meta, data, start, count);
  4138. } else {
  4139. parsed = this.parsePrimitiveData(meta, data, start, count);
  4140. }
  4141. const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);
  4142. for (i = 0; i < count; ++i) {
  4143. meta._parsed[i + start] = cur = parsed[i];
  4144. if (sorted) {
  4145. if (isNotInOrderComparedToPrev()) {
  4146. sorted = false;
  4147. }
  4148. prev = cur;
  4149. }
  4150. }
  4151. meta._sorted = sorted;
  4152. }
  4153. if (_stacked) {
  4154. updateStacks(this, parsed);
  4155. }
  4156. }
  4157. parsePrimitiveData(meta, data, start, count) {
  4158. const {iScale, vScale} = meta;
  4159. const iAxis = iScale.axis;
  4160. const vAxis = vScale.axis;
  4161. const labels = iScale.getLabels();
  4162. const singleScale = iScale === vScale;
  4163. const parsed = new Array(count);
  4164. let i, ilen, index;
  4165. for (i = 0, ilen = count; i < ilen; ++i) {
  4166. index = i + start;
  4167. parsed[i] = {
  4168. [iAxis]: singleScale || iScale.parse(labels[index], index),
  4169. [vAxis]: vScale.parse(data[index], index)
  4170. };
  4171. }
  4172. return parsed;
  4173. }
  4174. parseArrayData(meta, data, start, count) {
  4175. const {xScale, yScale} = meta;
  4176. const parsed = new Array(count);
  4177. let i, ilen, index, item;
  4178. for (i = 0, ilen = count; i < ilen; ++i) {
  4179. index = i + start;
  4180. item = data[index];
  4181. parsed[i] = {
  4182. x: xScale.parse(item[0], index),
  4183. y: yScale.parse(item[1], index)
  4184. };
  4185. }
  4186. return parsed;
  4187. }
  4188. parseObjectData(meta, data, start, count) {
  4189. const {xScale, yScale} = meta;
  4190. const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
  4191. const parsed = new Array(count);
  4192. let i, ilen, index, item;
  4193. for (i = 0, ilen = count; i < ilen; ++i) {
  4194. index = i + start;
  4195. item = data[index];
  4196. parsed[i] = {
  4197. x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
  4198. y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
  4199. };
  4200. }
  4201. return parsed;
  4202. }
  4203. getParsed(index) {
  4204. return this._cachedMeta._parsed[index];
  4205. }
  4206. getDataElement(index) {
  4207. return this._cachedMeta.data[index];
  4208. }
  4209. applyStack(scale, parsed, mode) {
  4210. const chart = this.chart;
  4211. const meta = this._cachedMeta;
  4212. const value = parsed[scale.axis];
  4213. const stack = {
  4214. keys: getSortedDatasetIndices(chart, true),
  4215. values: parsed._stacks[scale.axis]
  4216. };
  4217. return applyStack(stack, value, meta.index, {mode});
  4218. }
  4219. updateRangeFromParsed(range, scale, parsed, stack) {
  4220. const parsedValue = parsed[scale.axis];
  4221. let value = parsedValue === null ? NaN : parsedValue;
  4222. const values = stack && parsed._stacks[scale.axis];
  4223. if (stack && values) {
  4224. stack.values = values;
  4225. value = applyStack(stack, parsedValue, this._cachedMeta.index);
  4226. }
  4227. range.min = Math.min(range.min, value);
  4228. range.max = Math.max(range.max, value);
  4229. }
  4230. getMinMax(scale, canStack) {
  4231. const meta = this._cachedMeta;
  4232. const _parsed = meta._parsed;
  4233. const sorted = meta._sorted && scale === meta.iScale;
  4234. const ilen = _parsed.length;
  4235. const otherScale = this._getOtherScale(scale);
  4236. const stack = createStack(canStack, meta, this.chart);
  4237. const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};
  4238. const {min: otherMin, max: otherMax} = getUserBounds(otherScale);
  4239. let i, parsed;
  4240. function _skip() {
  4241. parsed = _parsed[i];
  4242. const otherValue = parsed[otherScale.axis];
  4243. return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
  4244. }
  4245. for (i = 0; i < ilen; ++i) {
  4246. if (_skip()) {
  4247. continue;
  4248. }
  4249. this.updateRangeFromParsed(range, scale, parsed, stack);
  4250. if (sorted) {
  4251. break;
  4252. }
  4253. }
  4254. if (sorted) {
  4255. for (i = ilen - 1; i >= 0; --i) {
  4256. if (_skip()) {
  4257. continue;
  4258. }
  4259. this.updateRangeFromParsed(range, scale, parsed, stack);
  4260. break;
  4261. }
  4262. }
  4263. return range;
  4264. }
  4265. getAllParsedValues(scale) {
  4266. const parsed = this._cachedMeta._parsed;
  4267. const values = [];
  4268. let i, ilen, value;
  4269. for (i = 0, ilen = parsed.length; i < ilen; ++i) {
  4270. value = parsed[i][scale.axis];
  4271. if (isNumberFinite(value)) {
  4272. values.push(value);
  4273. }
  4274. }
  4275. return values;
  4276. }
  4277. getMaxOverflow() {
  4278. return false;
  4279. }
  4280. getLabelAndValue(index) {
  4281. const meta = this._cachedMeta;
  4282. const iScale = meta.iScale;
  4283. const vScale = meta.vScale;
  4284. const parsed = this.getParsed(index);
  4285. return {
  4286. label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
  4287. value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
  4288. };
  4289. }
  4290. _update(mode) {
  4291. const meta = this._cachedMeta;
  4292. this.update(mode || 'default');
  4293. meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
  4294. }
  4295. update(mode) {}
  4296. draw() {
  4297. const ctx = this._ctx;
  4298. const chart = this.chart;
  4299. const meta = this._cachedMeta;
  4300. const elements = meta.data || [];
  4301. const area = chart.chartArea;
  4302. const active = [];
  4303. const start = this._drawStart || 0;
  4304. const count = this._drawCount || (elements.length - start);
  4305. const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
  4306. let i;
  4307. if (meta.dataset) {
  4308. meta.dataset.draw(ctx, area, start, count);
  4309. }
  4310. for (i = start; i < start + count; ++i) {
  4311. const element = elements[i];
  4312. if (element.hidden) {
  4313. continue;
  4314. }
  4315. if (element.active && drawActiveElementsOnTop) {
  4316. active.push(element);
  4317. } else {
  4318. element.draw(ctx, area);
  4319. }
  4320. }
  4321. for (i = 0; i < active.length; ++i) {
  4322. active[i].draw(ctx, area);
  4323. }
  4324. }
  4325. getStyle(index, active) {
  4326. const mode = active ? 'active' : 'default';
  4327. return index === undefined && this._cachedMeta.dataset
  4328. ? this.resolveDatasetElementOptions(mode)
  4329. : this.resolveDataElementOptions(index || 0, mode);
  4330. }
  4331. getContext(index, active, mode) {
  4332. const dataset = this.getDataset();
  4333. let context;
  4334. if (index >= 0 && index < this._cachedMeta.data.length) {
  4335. const element = this._cachedMeta.data[index];
  4336. context = element.$context ||
  4337. (element.$context = createDataContext(this.getContext(), index, element));
  4338. context.parsed = this.getParsed(index);
  4339. context.raw = dataset.data[index];
  4340. context.index = context.dataIndex = index;
  4341. } else {
  4342. context = this.$context ||
  4343. (this.$context = createDatasetContext(this.chart.getContext(), this.index));
  4344. context.dataset = dataset;
  4345. context.index = context.datasetIndex = this.index;
  4346. }
  4347. context.active = !!active;
  4348. context.mode = mode;
  4349. return context;
  4350. }
  4351. resolveDatasetElementOptions(mode) {
  4352. return this._resolveElementOptions(this.datasetElementType.id, mode);
  4353. }
  4354. resolveDataElementOptions(index, mode) {
  4355. return this._resolveElementOptions(this.dataElementType.id, mode, index);
  4356. }
  4357. _resolveElementOptions(elementType, mode = 'default', index) {
  4358. const active = mode === 'active';
  4359. const cache = this._cachedDataOpts;
  4360. const cacheKey = elementType + '-' + mode;
  4361. const cached = cache[cacheKey];
  4362. const sharing = this.enableOptionSharing && defined(index);
  4363. if (cached) {
  4364. return cloneIfNotShared(cached, sharing);
  4365. }
  4366. const config = this.chart.config;
  4367. const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
  4368. const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, ''];
  4369. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  4370. const names = Object.keys(defaults.elements[elementType]);
  4371. const context = () => this.getContext(index, active);
  4372. const values = config.resolveNamedOptions(scopes, names, context, prefixes);
  4373. if (values.$shared) {
  4374. values.$shared = sharing;
  4375. cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
  4376. }
  4377. return values;
  4378. }
  4379. _resolveAnimations(index, transition, active) {
  4380. const chart = this.chart;
  4381. const cache = this._cachedDataOpts;
  4382. const cacheKey = `animation-${transition}`;
  4383. const cached = cache[cacheKey];
  4384. if (cached) {
  4385. return cached;
  4386. }
  4387. let options;
  4388. if (chart.options.animation !== false) {
  4389. const config = this.chart.config;
  4390. const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
  4391. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  4392. options = config.createResolver(scopes, this.getContext(index, active, transition));
  4393. }
  4394. const animations = new Animations(chart, options && options.animations);
  4395. if (options && options._cacheable) {
  4396. cache[cacheKey] = Object.freeze(animations);
  4397. }
  4398. return animations;
  4399. }
  4400. getSharedOptions(options) {
  4401. if (!options.$shared) {
  4402. return;
  4403. }
  4404. return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
  4405. }
  4406. includeOptions(mode, sharedOptions) {
  4407. return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
  4408. }
  4409. updateElement(element, index, properties, mode) {
  4410. if (isDirectUpdateMode(mode)) {
  4411. Object.assign(element, properties);
  4412. } else {
  4413. this._resolveAnimations(index, mode).update(element, properties);
  4414. }
  4415. }
  4416. updateSharedOptions(sharedOptions, mode, newOptions) {
  4417. if (sharedOptions && !isDirectUpdateMode(mode)) {
  4418. this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
  4419. }
  4420. }
  4421. _setStyle(element, index, mode, active) {
  4422. element.active = active;
  4423. const options = this.getStyle(index, active);
  4424. this._resolveAnimations(index, mode, active).update(element, {
  4425. options: (!active && this.getSharedOptions(options)) || options
  4426. });
  4427. }
  4428. removeHoverStyle(element, datasetIndex, index) {
  4429. this._setStyle(element, index, 'active', false);
  4430. }
  4431. setHoverStyle(element, datasetIndex, index) {
  4432. this._setStyle(element, index, 'active', true);
  4433. }
  4434. _removeDatasetHoverStyle() {
  4435. const element = this._cachedMeta.dataset;
  4436. if (element) {
  4437. this._setStyle(element, undefined, 'active', false);
  4438. }
  4439. }
  4440. _setDatasetHoverStyle() {
  4441. const element = this._cachedMeta.dataset;
  4442. if (element) {
  4443. this._setStyle(element, undefined, 'active', true);
  4444. }
  4445. }
  4446. _resyncElements(resetNewElements) {
  4447. const data = this._data;
  4448. const elements = this._cachedMeta.data;
  4449. for (const [method, arg1, arg2] of this._syncList) {
  4450. this[method](arg1, arg2);
  4451. }
  4452. this._syncList = [];
  4453. const numMeta = elements.length;
  4454. const numData = data.length;
  4455. const count = Math.min(numData, numMeta);
  4456. if (count) {
  4457. this.parse(0, count);
  4458. }
  4459. if (numData > numMeta) {
  4460. this._insertElements(numMeta, numData - numMeta, resetNewElements);
  4461. } else if (numData < numMeta) {
  4462. this._removeElements(numData, numMeta - numData);
  4463. }
  4464. }
  4465. _insertElements(start, count, resetNewElements = true) {
  4466. const meta = this._cachedMeta;
  4467. const data = meta.data;
  4468. const end = start + count;
  4469. let i;
  4470. const move = (arr) => {
  4471. arr.length += count;
  4472. for (i = arr.length - 1; i >= end; i--) {
  4473. arr[i] = arr[i - count];
  4474. }
  4475. };
  4476. move(data);
  4477. for (i = start; i < end; ++i) {
  4478. data[i] = new this.dataElementType();
  4479. }
  4480. if (this._parsing) {
  4481. move(meta._parsed);
  4482. }
  4483. this.parse(start, count);
  4484. if (resetNewElements) {
  4485. this.updateElements(data, start, count, 'reset');
  4486. }
  4487. }
  4488. updateElements(element, start, count, mode) {}
  4489. _removeElements(start, count) {
  4490. const meta = this._cachedMeta;
  4491. if (this._parsing) {
  4492. const removed = meta._parsed.splice(start, count);
  4493. if (meta._stacked) {
  4494. clearStacks(meta, removed);
  4495. }
  4496. }
  4497. meta.data.splice(start, count);
  4498. }
  4499. _sync(args) {
  4500. if (this._parsing) {
  4501. this._syncList.push(args);
  4502. } else {
  4503. const [method, arg1, arg2] = args;
  4504. this[method](arg1, arg2);
  4505. }
  4506. this.chart._dataChanges.push([this.index, ...args]);
  4507. }
  4508. _onDataPush() {
  4509. const count = arguments.length;
  4510. this._sync(['_insertElements', this.getDataset().data.length - count, count]);
  4511. }
  4512. _onDataPop() {
  4513. this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]);
  4514. }
  4515. _onDataShift() {
  4516. this._sync(['_removeElements', 0, 1]);
  4517. }
  4518. _onDataSplice(start, count) {
  4519. if (count) {
  4520. this._sync(['_removeElements', start, count]);
  4521. }
  4522. const newCount = arguments.length - 2;
  4523. if (newCount) {
  4524. this._sync(['_insertElements', start, newCount]);
  4525. }
  4526. }
  4527. _onDataUnshift() {
  4528. this._sync(['_insertElements', 0, arguments.length]);
  4529. }
  4530. }
  4531. DatasetController.defaults = {};
  4532. DatasetController.prototype.datasetElementType = null;
  4533. DatasetController.prototype.dataElementType = null;
  4534. class Element {
  4535. constructor() {
  4536. this.x = undefined;
  4537. this.y = undefined;
  4538. this.active = false;
  4539. this.options = undefined;
  4540. this.$animations = undefined;
  4541. }
  4542. tooltipPosition(useFinalPosition) {
  4543. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  4544. return {x, y};
  4545. }
  4546. hasValue() {
  4547. return isNumber(this.x) && isNumber(this.y);
  4548. }
  4549. getProps(props, final) {
  4550. const anims = this.$animations;
  4551. if (!final || !anims) {
  4552. return this;
  4553. }
  4554. const ret = {};
  4555. props.forEach(prop => {
  4556. ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
  4557. });
  4558. return ret;
  4559. }
  4560. }
  4561. Element.defaults = {};
  4562. Element.defaultRoutes = undefined;
  4563. const formatters = {
  4564. values(value) {
  4565. return isArray(value) ? value : '' + value;
  4566. },
  4567. numeric(tickValue, index, ticks) {
  4568. if (tickValue === 0) {
  4569. return '0';
  4570. }
  4571. const locale = this.chart.options.locale;
  4572. let notation;
  4573. let delta = tickValue;
  4574. if (ticks.length > 1) {
  4575. const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
  4576. if (maxTick < 1e-4 || maxTick > 1e+15) {
  4577. notation = 'scientific';
  4578. }
  4579. delta = calculateDelta(tickValue, ticks);
  4580. }
  4581. const logDelta = log10(Math.abs(delta));
  4582. const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
  4583. const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal};
  4584. Object.assign(options, this.options.ticks.format);
  4585. return formatNumber(tickValue, locale, options);
  4586. },
  4587. logarithmic(tickValue, index, ticks) {
  4588. if (tickValue === 0) {
  4589. return '0';
  4590. }
  4591. const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue))));
  4592. if (remain === 1 || remain === 2 || remain === 5) {
  4593. return formatters.numeric.call(this, tickValue, index, ticks);
  4594. }
  4595. return '';
  4596. }
  4597. };
  4598. function calculateDelta(tickValue, ticks) {
  4599. let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
  4600. if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
  4601. delta = tickValue - Math.floor(tickValue);
  4602. }
  4603. return delta;
  4604. }
  4605. var Ticks = {formatters};
  4606. defaults.set('scale', {
  4607. display: true,
  4608. offset: false,
  4609. reverse: false,
  4610. beginAtZero: false,
  4611. bounds: 'ticks',
  4612. grace: 0,
  4613. grid: {
  4614. display: true,
  4615. lineWidth: 1,
  4616. drawBorder: true,
  4617. drawOnChartArea: true,
  4618. drawTicks: true,
  4619. tickLength: 8,
  4620. tickWidth: (_ctx, options) => options.lineWidth,
  4621. tickColor: (_ctx, options) => options.color,
  4622. offset: false,
  4623. borderDash: [],
  4624. borderDashOffset: 0.0,
  4625. borderWidth: 1
  4626. },
  4627. title: {
  4628. display: false,
  4629. text: '',
  4630. padding: {
  4631. top: 4,
  4632. bottom: 4
  4633. }
  4634. },
  4635. ticks: {
  4636. minRotation: 0,
  4637. maxRotation: 50,
  4638. mirror: false,
  4639. textStrokeWidth: 0,
  4640. textStrokeColor: '',
  4641. padding: 3,
  4642. display: true,
  4643. autoSkip: true,
  4644. autoSkipPadding: 3,
  4645. labelOffset: 0,
  4646. callback: Ticks.formatters.values,
  4647. minor: {},
  4648. major: {},
  4649. align: 'center',
  4650. crossAlign: 'near',
  4651. showLabelBackdrop: false,
  4652. backdropColor: 'rgba(255, 255, 255, 0.75)',
  4653. backdropPadding: 2,
  4654. }
  4655. });
  4656. defaults.route('scale.ticks', 'color', '', 'color');
  4657. defaults.route('scale.grid', 'color', '', 'borderColor');
  4658. defaults.route('scale.grid', 'borderColor', '', 'borderColor');
  4659. defaults.route('scale.title', 'color', '', 'color');
  4660. defaults.describe('scale', {
  4661. _fallback: false,
  4662. _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
  4663. _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
  4664. });
  4665. defaults.describe('scales', {
  4666. _fallback: 'scale',
  4667. });
  4668. defaults.describe('scale.ticks', {
  4669. _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback',
  4670. _indexable: (name) => name !== 'backdropPadding',
  4671. });
  4672. function autoSkip(scale, ticks) {
  4673. const tickOpts = scale.options.ticks;
  4674. const ticksLimit = tickOpts.maxTicksLimit || determineMaxTicks(scale);
  4675. const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
  4676. const numMajorIndices = majorIndices.length;
  4677. const first = majorIndices[0];
  4678. const last = majorIndices[numMajorIndices - 1];
  4679. const newTicks = [];
  4680. if (numMajorIndices > ticksLimit) {
  4681. skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
  4682. return newTicks;
  4683. }
  4684. const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
  4685. if (numMajorIndices > 0) {
  4686. let i, ilen;
  4687. const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
  4688. skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
  4689. for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
  4690. skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
  4691. }
  4692. skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
  4693. return newTicks;
  4694. }
  4695. skip(ticks, newTicks, spacing);
  4696. return newTicks;
  4697. }
  4698. function determineMaxTicks(scale) {
  4699. const offset = scale.options.offset;
  4700. const tickLength = scale._tickSize();
  4701. const maxScale = scale._length / tickLength + (offset ? 0 : 1);
  4702. const maxChart = scale._maxLength / tickLength;
  4703. return Math.floor(Math.min(maxScale, maxChart));
  4704. }
  4705. function calculateSpacing(majorIndices, ticks, ticksLimit) {
  4706. const evenMajorSpacing = getEvenSpacing(majorIndices);
  4707. const spacing = ticks.length / ticksLimit;
  4708. if (!evenMajorSpacing) {
  4709. return Math.max(spacing, 1);
  4710. }
  4711. const factors = _factorize(evenMajorSpacing);
  4712. for (let i = 0, ilen = factors.length - 1; i < ilen; i++) {
  4713. const factor = factors[i];
  4714. if (factor > spacing) {
  4715. return factor;
  4716. }
  4717. }
  4718. return Math.max(spacing, 1);
  4719. }
  4720. function getMajorIndices(ticks) {
  4721. const result = [];
  4722. let i, ilen;
  4723. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  4724. if (ticks[i].major) {
  4725. result.push(i);
  4726. }
  4727. }
  4728. return result;
  4729. }
  4730. function skipMajors(ticks, newTicks, majorIndices, spacing) {
  4731. let count = 0;
  4732. let next = majorIndices[0];
  4733. let i;
  4734. spacing = Math.ceil(spacing);
  4735. for (i = 0; i < ticks.length; i++) {
  4736. if (i === next) {
  4737. newTicks.push(ticks[i]);
  4738. count++;
  4739. next = majorIndices[count * spacing];
  4740. }
  4741. }
  4742. }
  4743. function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
  4744. const start = valueOrDefault(majorStart, 0);
  4745. const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
  4746. let count = 0;
  4747. let length, i, next;
  4748. spacing = Math.ceil(spacing);
  4749. if (majorEnd) {
  4750. length = majorEnd - majorStart;
  4751. spacing = length / Math.floor(length / spacing);
  4752. }
  4753. next = start;
  4754. while (next < 0) {
  4755. count++;
  4756. next = Math.round(start + count * spacing);
  4757. }
  4758. for (i = Math.max(start, 0); i < end; i++) {
  4759. if (i === next) {
  4760. newTicks.push(ticks[i]);
  4761. count++;
  4762. next = Math.round(start + count * spacing);
  4763. }
  4764. }
  4765. }
  4766. function getEvenSpacing(arr) {
  4767. const len = arr.length;
  4768. let i, diff;
  4769. if (len < 2) {
  4770. return false;
  4771. }
  4772. for (diff = arr[0], i = 1; i < len; ++i) {
  4773. if (arr[i] - arr[i - 1] !== diff) {
  4774. return false;
  4775. }
  4776. }
  4777. return diff;
  4778. }
  4779. const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  4780. const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  4781. function sample(arr, numItems) {
  4782. const result = [];
  4783. const increment = arr.length / numItems;
  4784. const len = arr.length;
  4785. let i = 0;
  4786. for (; i < len; i += increment) {
  4787. result.push(arr[Math.floor(i)]);
  4788. }
  4789. return result;
  4790. }
  4791. function getPixelForGridLine(scale, index, offsetGridLines) {
  4792. const length = scale.ticks.length;
  4793. const validIndex = Math.min(index, length - 1);
  4794. const start = scale._startPixel;
  4795. const end = scale._endPixel;
  4796. const epsilon = 1e-6;
  4797. let lineValue = scale.getPixelForTick(validIndex);
  4798. let offset;
  4799. if (offsetGridLines) {
  4800. if (length === 1) {
  4801. offset = Math.max(lineValue - start, end - lineValue);
  4802. } else if (index === 0) {
  4803. offset = (scale.getPixelForTick(1) - lineValue) / 2;
  4804. } else {
  4805. offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
  4806. }
  4807. lineValue += validIndex < index ? offset : -offset;
  4808. if (lineValue < start - epsilon || lineValue > end + epsilon) {
  4809. return;
  4810. }
  4811. }
  4812. return lineValue;
  4813. }
  4814. function garbageCollect(caches, length) {
  4815. each(caches, (cache) => {
  4816. const gc = cache.gc;
  4817. const gcLen = gc.length / 2;
  4818. let i;
  4819. if (gcLen > length) {
  4820. for (i = 0; i < gcLen; ++i) {
  4821. delete cache.data[gc[i]];
  4822. }
  4823. gc.splice(0, gcLen);
  4824. }
  4825. });
  4826. }
  4827. function getTickMarkLength(options) {
  4828. return options.drawTicks ? options.tickLength : 0;
  4829. }
  4830. function getTitleHeight(options, fallback) {
  4831. if (!options.display) {
  4832. return 0;
  4833. }
  4834. const font = toFont(options.font, fallback);
  4835. const padding = toPadding(options.padding);
  4836. const lines = isArray(options.text) ? options.text.length : 1;
  4837. return (lines * font.lineHeight) + padding.height;
  4838. }
  4839. function createScaleContext(parent, scale) {
  4840. return createContext(parent, {
  4841. scale,
  4842. type: 'scale'
  4843. });
  4844. }
  4845. function createTickContext(parent, index, tick) {
  4846. return createContext(parent, {
  4847. tick,
  4848. index,
  4849. type: 'tick'
  4850. });
  4851. }
  4852. function titleAlign(align, position, reverse) {
  4853. let ret = _toLeftRightCenter(align);
  4854. if ((reverse && position !== 'right') || (!reverse && position === 'right')) {
  4855. ret = reverseAlign(ret);
  4856. }
  4857. return ret;
  4858. }
  4859. function titleArgs(scale, offset, position, align) {
  4860. const {top, left, bottom, right, chart} = scale;
  4861. const {chartArea, scales} = chart;
  4862. let rotation = 0;
  4863. let maxWidth, titleX, titleY;
  4864. const height = bottom - top;
  4865. const width = right - left;
  4866. if (scale.isHorizontal()) {
  4867. titleX = _alignStartEnd(align, left, right);
  4868. if (isObject(position)) {
  4869. const positionAxisID = Object.keys(position)[0];
  4870. const value = position[positionAxisID];
  4871. titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
  4872. } else if (position === 'center') {
  4873. titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
  4874. } else {
  4875. titleY = offsetFromEdge(scale, position, offset);
  4876. }
  4877. maxWidth = right - left;
  4878. } else {
  4879. if (isObject(position)) {
  4880. const positionAxisID = Object.keys(position)[0];
  4881. const value = position[positionAxisID];
  4882. titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
  4883. } else if (position === 'center') {
  4884. titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
  4885. } else {
  4886. titleX = offsetFromEdge(scale, position, offset);
  4887. }
  4888. titleY = _alignStartEnd(align, bottom, top);
  4889. rotation = position === 'left' ? -HALF_PI : HALF_PI;
  4890. }
  4891. return {titleX, titleY, maxWidth, rotation};
  4892. }
  4893. class Scale extends Element {
  4894. constructor(cfg) {
  4895. super();
  4896. this.id = cfg.id;
  4897. this.type = cfg.type;
  4898. this.options = undefined;
  4899. this.ctx = cfg.ctx;
  4900. this.chart = cfg.chart;
  4901. this.top = undefined;
  4902. this.bottom = undefined;
  4903. this.left = undefined;
  4904. this.right = undefined;
  4905. this.width = undefined;
  4906. this.height = undefined;
  4907. this._margins = {
  4908. left: 0,
  4909. right: 0,
  4910. top: 0,
  4911. bottom: 0
  4912. };
  4913. this.maxWidth = undefined;
  4914. this.maxHeight = undefined;
  4915. this.paddingTop = undefined;
  4916. this.paddingBottom = undefined;
  4917. this.paddingLeft = undefined;
  4918. this.paddingRight = undefined;
  4919. this.axis = undefined;
  4920. this.labelRotation = undefined;
  4921. this.min = undefined;
  4922. this.max = undefined;
  4923. this._range = undefined;
  4924. this.ticks = [];
  4925. this._gridLineItems = null;
  4926. this._labelItems = null;
  4927. this._labelSizes = null;
  4928. this._length = 0;
  4929. this._maxLength = 0;
  4930. this._longestTextCache = {};
  4931. this._startPixel = undefined;
  4932. this._endPixel = undefined;
  4933. this._reversePixels = false;
  4934. this._userMax = undefined;
  4935. this._userMin = undefined;
  4936. this._suggestedMax = undefined;
  4937. this._suggestedMin = undefined;
  4938. this._ticksLength = 0;
  4939. this._borderValue = 0;
  4940. this._cache = {};
  4941. this._dataLimitsCached = false;
  4942. this.$context = undefined;
  4943. }
  4944. init(options) {
  4945. this.options = options.setContext(this.getContext());
  4946. this.axis = options.axis;
  4947. this._userMin = this.parse(options.min);
  4948. this._userMax = this.parse(options.max);
  4949. this._suggestedMin = this.parse(options.suggestedMin);
  4950. this._suggestedMax = this.parse(options.suggestedMax);
  4951. }
  4952. parse(raw, index) {
  4953. return raw;
  4954. }
  4955. getUserBounds() {
  4956. let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this;
  4957. _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
  4958. _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
  4959. _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
  4960. _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
  4961. return {
  4962. min: finiteOrDefault(_userMin, _suggestedMin),
  4963. max: finiteOrDefault(_userMax, _suggestedMax),
  4964. minDefined: isNumberFinite(_userMin),
  4965. maxDefined: isNumberFinite(_userMax)
  4966. };
  4967. }
  4968. getMinMax(canStack) {
  4969. let {min, max, minDefined, maxDefined} = this.getUserBounds();
  4970. let range;
  4971. if (minDefined && maxDefined) {
  4972. return {min, max};
  4973. }
  4974. const metas = this.getMatchingVisibleMetas();
  4975. for (let i = 0, ilen = metas.length; i < ilen; ++i) {
  4976. range = metas[i].controller.getMinMax(this, canStack);
  4977. if (!minDefined) {
  4978. min = Math.min(min, range.min);
  4979. }
  4980. if (!maxDefined) {
  4981. max = Math.max(max, range.max);
  4982. }
  4983. }
  4984. min = maxDefined && min > max ? max : min;
  4985. max = minDefined && min > max ? min : max;
  4986. return {
  4987. min: finiteOrDefault(min, finiteOrDefault(max, min)),
  4988. max: finiteOrDefault(max, finiteOrDefault(min, max))
  4989. };
  4990. }
  4991. getPadding() {
  4992. return {
  4993. left: this.paddingLeft || 0,
  4994. top: this.paddingTop || 0,
  4995. right: this.paddingRight || 0,
  4996. bottom: this.paddingBottom || 0
  4997. };
  4998. }
  4999. getTicks() {
  5000. return this.ticks;
  5001. }
  5002. getLabels() {
  5003. const data = this.chart.data;
  5004. return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
  5005. }
  5006. beforeLayout() {
  5007. this._cache = {};
  5008. this._dataLimitsCached = false;
  5009. }
  5010. beforeUpdate() {
  5011. callback(this.options.beforeUpdate, [this]);
  5012. }
  5013. update(maxWidth, maxHeight, margins) {
  5014. const {beginAtZero, grace, ticks: tickOpts} = this.options;
  5015. const sampleSize = tickOpts.sampleSize;
  5016. this.beforeUpdate();
  5017. this.maxWidth = maxWidth;
  5018. this.maxHeight = maxHeight;
  5019. this._margins = margins = Object.assign({
  5020. left: 0,
  5021. right: 0,
  5022. top: 0,
  5023. bottom: 0
  5024. }, margins);
  5025. this.ticks = null;
  5026. this._labelSizes = null;
  5027. this._gridLineItems = null;
  5028. this._labelItems = null;
  5029. this.beforeSetDimensions();
  5030. this.setDimensions();
  5031. this.afterSetDimensions();
  5032. this._maxLength = this.isHorizontal()
  5033. ? this.width + margins.left + margins.right
  5034. : this.height + margins.top + margins.bottom;
  5035. if (!this._dataLimitsCached) {
  5036. this.beforeDataLimits();
  5037. this.determineDataLimits();
  5038. this.afterDataLimits();
  5039. this._range = _addGrace(this, grace, beginAtZero);
  5040. this._dataLimitsCached = true;
  5041. }
  5042. this.beforeBuildTicks();
  5043. this.ticks = this.buildTicks() || [];
  5044. this.afterBuildTicks();
  5045. const samplingEnabled = sampleSize < this.ticks.length;
  5046. this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
  5047. this.configure();
  5048. this.beforeCalculateLabelRotation();
  5049. this.calculateLabelRotation();
  5050. this.afterCalculateLabelRotation();
  5051. if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
  5052. this.ticks = autoSkip(this, this.ticks);
  5053. this._labelSizes = null;
  5054. }
  5055. if (samplingEnabled) {
  5056. this._convertTicksToLabels(this.ticks);
  5057. }
  5058. this.beforeFit();
  5059. this.fit();
  5060. this.afterFit();
  5061. this.afterUpdate();
  5062. }
  5063. configure() {
  5064. let reversePixels = this.options.reverse;
  5065. let startPixel, endPixel;
  5066. if (this.isHorizontal()) {
  5067. startPixel = this.left;
  5068. endPixel = this.right;
  5069. } else {
  5070. startPixel = this.top;
  5071. endPixel = this.bottom;
  5072. reversePixels = !reversePixels;
  5073. }
  5074. this._startPixel = startPixel;
  5075. this._endPixel = endPixel;
  5076. this._reversePixels = reversePixels;
  5077. this._length = endPixel - startPixel;
  5078. this._alignToPixels = this.options.alignToPixels;
  5079. }
  5080. afterUpdate() {
  5081. callback(this.options.afterUpdate, [this]);
  5082. }
  5083. beforeSetDimensions() {
  5084. callback(this.options.beforeSetDimensions, [this]);
  5085. }
  5086. setDimensions() {
  5087. if (this.isHorizontal()) {
  5088. this.width = this.maxWidth;
  5089. this.left = 0;
  5090. this.right = this.width;
  5091. } else {
  5092. this.height = this.maxHeight;
  5093. this.top = 0;
  5094. this.bottom = this.height;
  5095. }
  5096. this.paddingLeft = 0;
  5097. this.paddingTop = 0;
  5098. this.paddingRight = 0;
  5099. this.paddingBottom = 0;
  5100. }
  5101. afterSetDimensions() {
  5102. callback(this.options.afterSetDimensions, [this]);
  5103. }
  5104. _callHooks(name) {
  5105. this.chart.notifyPlugins(name, this.getContext());
  5106. callback(this.options[name], [this]);
  5107. }
  5108. beforeDataLimits() {
  5109. this._callHooks('beforeDataLimits');
  5110. }
  5111. determineDataLimits() {}
  5112. afterDataLimits() {
  5113. this._callHooks('afterDataLimits');
  5114. }
  5115. beforeBuildTicks() {
  5116. this._callHooks('beforeBuildTicks');
  5117. }
  5118. buildTicks() {
  5119. return [];
  5120. }
  5121. afterBuildTicks() {
  5122. this._callHooks('afterBuildTicks');
  5123. }
  5124. beforeTickToLabelConversion() {
  5125. callback(this.options.beforeTickToLabelConversion, [this]);
  5126. }
  5127. generateTickLabels(ticks) {
  5128. const tickOpts = this.options.ticks;
  5129. let i, ilen, tick;
  5130. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  5131. tick = ticks[i];
  5132. tick.label = callback(tickOpts.callback, [tick.value, i, ticks], this);
  5133. }
  5134. }
  5135. afterTickToLabelConversion() {
  5136. callback(this.options.afterTickToLabelConversion, [this]);
  5137. }
  5138. beforeCalculateLabelRotation() {
  5139. callback(this.options.beforeCalculateLabelRotation, [this]);
  5140. }
  5141. calculateLabelRotation() {
  5142. const options = this.options;
  5143. const tickOpts = options.ticks;
  5144. const numTicks = this.ticks.length;
  5145. const minRotation = tickOpts.minRotation || 0;
  5146. const maxRotation = tickOpts.maxRotation;
  5147. let labelRotation = minRotation;
  5148. let tickWidth, maxHeight, maxLabelDiagonal;
  5149. if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
  5150. this.labelRotation = minRotation;
  5151. return;
  5152. }
  5153. const labelSizes = this._getLabelSizes();
  5154. const maxLabelWidth = labelSizes.widest.width;
  5155. const maxLabelHeight = labelSizes.highest.height;
  5156. const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
  5157. tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
  5158. if (maxLabelWidth + 6 > tickWidth) {
  5159. tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
  5160. maxHeight = this.maxHeight - getTickMarkLength(options.grid)
  5161. - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
  5162. maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
  5163. labelRotation = toDegrees(Math.min(
  5164. Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)),
  5165. Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))
  5166. ));
  5167. labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
  5168. }
  5169. this.labelRotation = labelRotation;
  5170. }
  5171. afterCalculateLabelRotation() {
  5172. callback(this.options.afterCalculateLabelRotation, [this]);
  5173. }
  5174. beforeFit() {
  5175. callback(this.options.beforeFit, [this]);
  5176. }
  5177. fit() {
  5178. const minSize = {
  5179. width: 0,
  5180. height: 0
  5181. };
  5182. const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = this;
  5183. const display = this._isVisible();
  5184. const isHorizontal = this.isHorizontal();
  5185. if (display) {
  5186. const titleHeight = getTitleHeight(titleOpts, chart.options.font);
  5187. if (isHorizontal) {
  5188. minSize.width = this.maxWidth;
  5189. minSize.height = getTickMarkLength(gridOpts) + titleHeight;
  5190. } else {
  5191. minSize.height = this.maxHeight;
  5192. minSize.width = getTickMarkLength(gridOpts) + titleHeight;
  5193. }
  5194. if (tickOpts.display && this.ticks.length) {
  5195. const {first, last, widest, highest} = this._getLabelSizes();
  5196. const tickPadding = tickOpts.padding * 2;
  5197. const angleRadians = toRadians(this.labelRotation);
  5198. const cos = Math.cos(angleRadians);
  5199. const sin = Math.sin(angleRadians);
  5200. if (isHorizontal) {
  5201. const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
  5202. minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
  5203. } else {
  5204. const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
  5205. minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
  5206. }
  5207. this._calculatePadding(first, last, sin, cos);
  5208. }
  5209. }
  5210. this._handleMargins();
  5211. if (isHorizontal) {
  5212. this.width = this._length = chart.width - this._margins.left - this._margins.right;
  5213. this.height = minSize.height;
  5214. } else {
  5215. this.width = minSize.width;
  5216. this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
  5217. }
  5218. }
  5219. _calculatePadding(first, last, sin, cos) {
  5220. const {ticks: {align, padding}, position} = this.options;
  5221. const isRotated = this.labelRotation !== 0;
  5222. const labelsBelowTicks = position !== 'top' && this.axis === 'x';
  5223. if (this.isHorizontal()) {
  5224. const offsetLeft = this.getPixelForTick(0) - this.left;
  5225. const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
  5226. let paddingLeft = 0;
  5227. let paddingRight = 0;
  5228. if (isRotated) {
  5229. if (labelsBelowTicks) {
  5230. paddingLeft = cos * first.width;
  5231. paddingRight = sin * last.height;
  5232. } else {
  5233. paddingLeft = sin * first.height;
  5234. paddingRight = cos * last.width;
  5235. }
  5236. } else if (align === 'start') {
  5237. paddingRight = last.width;
  5238. } else if (align === 'end') {
  5239. paddingLeft = first.width;
  5240. } else {
  5241. paddingLeft = first.width / 2;
  5242. paddingRight = last.width / 2;
  5243. }
  5244. this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
  5245. this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
  5246. } else {
  5247. let paddingTop = last.height / 2;
  5248. let paddingBottom = first.height / 2;
  5249. if (align === 'start') {
  5250. paddingTop = 0;
  5251. paddingBottom = first.height;
  5252. } else if (align === 'end') {
  5253. paddingTop = last.height;
  5254. paddingBottom = 0;
  5255. }
  5256. this.paddingTop = paddingTop + padding;
  5257. this.paddingBottom = paddingBottom + padding;
  5258. }
  5259. }
  5260. _handleMargins() {
  5261. if (this._margins) {
  5262. this._margins.left = Math.max(this.paddingLeft, this._margins.left);
  5263. this._margins.top = Math.max(this.paddingTop, this._margins.top);
  5264. this._margins.right = Math.max(this.paddingRight, this._margins.right);
  5265. this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
  5266. }
  5267. }
  5268. afterFit() {
  5269. callback(this.options.afterFit, [this]);
  5270. }
  5271. isHorizontal() {
  5272. const {axis, position} = this.options;
  5273. return position === 'top' || position === 'bottom' || axis === 'x';
  5274. }
  5275. isFullSize() {
  5276. return this.options.fullSize;
  5277. }
  5278. _convertTicksToLabels(ticks) {
  5279. this.beforeTickToLabelConversion();
  5280. this.generateTickLabels(ticks);
  5281. let i, ilen;
  5282. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  5283. if (isNullOrUndef(ticks[i].label)) {
  5284. ticks.splice(i, 1);
  5285. ilen--;
  5286. i--;
  5287. }
  5288. }
  5289. this.afterTickToLabelConversion();
  5290. }
  5291. _getLabelSizes() {
  5292. let labelSizes = this._labelSizes;
  5293. if (!labelSizes) {
  5294. const sampleSize = this.options.ticks.sampleSize;
  5295. let ticks = this.ticks;
  5296. if (sampleSize < ticks.length) {
  5297. ticks = sample(ticks, sampleSize);
  5298. }
  5299. this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length);
  5300. }
  5301. return labelSizes;
  5302. }
  5303. _computeLabelSizes(ticks, length) {
  5304. const {ctx, _longestTextCache: caches} = this;
  5305. const widths = [];
  5306. const heights = [];
  5307. let widestLabelSize = 0;
  5308. let highestLabelSize = 0;
  5309. let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
  5310. for (i = 0; i < length; ++i) {
  5311. label = ticks[i].label;
  5312. tickFont = this._resolveTickFontOptions(i);
  5313. ctx.font = fontString = tickFont.string;
  5314. cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
  5315. lineHeight = tickFont.lineHeight;
  5316. width = height = 0;
  5317. if (!isNullOrUndef(label) && !isArray(label)) {
  5318. width = _measureText(ctx, cache.data, cache.gc, width, label);
  5319. height = lineHeight;
  5320. } else if (isArray(label)) {
  5321. for (j = 0, jlen = label.length; j < jlen; ++j) {
  5322. nestedLabel = label[j];
  5323. if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
  5324. width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  5325. height += lineHeight;
  5326. }
  5327. }
  5328. }
  5329. widths.push(width);
  5330. heights.push(height);
  5331. widestLabelSize = Math.max(width, widestLabelSize);
  5332. highestLabelSize = Math.max(height, highestLabelSize);
  5333. }
  5334. garbageCollect(caches, length);
  5335. const widest = widths.indexOf(widestLabelSize);
  5336. const highest = heights.indexOf(highestLabelSize);
  5337. const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});
  5338. return {
  5339. first: valueAt(0),
  5340. last: valueAt(length - 1),
  5341. widest: valueAt(widest),
  5342. highest: valueAt(highest),
  5343. widths,
  5344. heights,
  5345. };
  5346. }
  5347. getLabelForValue(value) {
  5348. return value;
  5349. }
  5350. getPixelForValue(value, index) {
  5351. return NaN;
  5352. }
  5353. getValueForPixel(pixel) {}
  5354. getPixelForTick(index) {
  5355. const ticks = this.ticks;
  5356. if (index < 0 || index > ticks.length - 1) {
  5357. return null;
  5358. }
  5359. return this.getPixelForValue(ticks[index].value);
  5360. }
  5361. getPixelForDecimal(decimal) {
  5362. if (this._reversePixels) {
  5363. decimal = 1 - decimal;
  5364. }
  5365. const pixel = this._startPixel + decimal * this._length;
  5366. return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
  5367. }
  5368. getDecimalForPixel(pixel) {
  5369. const decimal = (pixel - this._startPixel) / this._length;
  5370. return this._reversePixels ? 1 - decimal : decimal;
  5371. }
  5372. getBasePixel() {
  5373. return this.getPixelForValue(this.getBaseValue());
  5374. }
  5375. getBaseValue() {
  5376. const {min, max} = this;
  5377. return min < 0 && max < 0 ? max :
  5378. min > 0 && max > 0 ? min :
  5379. 0;
  5380. }
  5381. getContext(index) {
  5382. const ticks = this.ticks || [];
  5383. if (index >= 0 && index < ticks.length) {
  5384. const tick = ticks[index];
  5385. return tick.$context ||
  5386. (tick.$context = createTickContext(this.getContext(), index, tick));
  5387. }
  5388. return this.$context ||
  5389. (this.$context = createScaleContext(this.chart.getContext(), this));
  5390. }
  5391. _tickSize() {
  5392. const optionTicks = this.options.ticks;
  5393. const rot = toRadians(this.labelRotation);
  5394. const cos = Math.abs(Math.cos(rot));
  5395. const sin = Math.abs(Math.sin(rot));
  5396. const labelSizes = this._getLabelSizes();
  5397. const padding = optionTicks.autoSkipPadding || 0;
  5398. const w = labelSizes ? labelSizes.widest.width + padding : 0;
  5399. const h = labelSizes ? labelSizes.highest.height + padding : 0;
  5400. return this.isHorizontal()
  5401. ? h * cos > w * sin ? w / cos : h / sin
  5402. : h * sin < w * cos ? h / cos : w / sin;
  5403. }
  5404. _isVisible() {
  5405. const display = this.options.display;
  5406. if (display !== 'auto') {
  5407. return !!display;
  5408. }
  5409. return this.getMatchingVisibleMetas().length > 0;
  5410. }
  5411. _computeGridLineItems(chartArea) {
  5412. const axis = this.axis;
  5413. const chart = this.chart;
  5414. const options = this.options;
  5415. const {grid, position} = options;
  5416. const offset = grid.offset;
  5417. const isHorizontal = this.isHorizontal();
  5418. const ticks = this.ticks;
  5419. const ticksLength = ticks.length + (offset ? 1 : 0);
  5420. const tl = getTickMarkLength(grid);
  5421. const items = [];
  5422. const borderOpts = grid.setContext(this.getContext());
  5423. const axisWidth = borderOpts.drawBorder ? borderOpts.borderWidth : 0;
  5424. const axisHalfWidth = axisWidth / 2;
  5425. const alignBorderValue = function(pixel) {
  5426. return _alignPixel(chart, pixel, axisWidth);
  5427. };
  5428. let borderValue, i, lineValue, alignedLineValue;
  5429. let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
  5430. if (position === 'top') {
  5431. borderValue = alignBorderValue(this.bottom);
  5432. ty1 = this.bottom - tl;
  5433. ty2 = borderValue - axisHalfWidth;
  5434. y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
  5435. y2 = chartArea.bottom;
  5436. } else if (position === 'bottom') {
  5437. borderValue = alignBorderValue(this.top);
  5438. y1 = chartArea.top;
  5439. y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
  5440. ty1 = borderValue + axisHalfWidth;
  5441. ty2 = this.top + tl;
  5442. } else if (position === 'left') {
  5443. borderValue = alignBorderValue(this.right);
  5444. tx1 = this.right - tl;
  5445. tx2 = borderValue - axisHalfWidth;
  5446. x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
  5447. x2 = chartArea.right;
  5448. } else if (position === 'right') {
  5449. borderValue = alignBorderValue(this.left);
  5450. x1 = chartArea.left;
  5451. x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
  5452. tx1 = borderValue + axisHalfWidth;
  5453. tx2 = this.left + tl;
  5454. } else if (axis === 'x') {
  5455. if (position === 'center') {
  5456. borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
  5457. } else if (isObject(position)) {
  5458. const positionAxisID = Object.keys(position)[0];
  5459. const value = position[positionAxisID];
  5460. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  5461. }
  5462. y1 = chartArea.top;
  5463. y2 = chartArea.bottom;
  5464. ty1 = borderValue + axisHalfWidth;
  5465. ty2 = ty1 + tl;
  5466. } else if (axis === 'y') {
  5467. if (position === 'center') {
  5468. borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
  5469. } else if (isObject(position)) {
  5470. const positionAxisID = Object.keys(position)[0];
  5471. const value = position[positionAxisID];
  5472. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  5473. }
  5474. tx1 = borderValue - axisHalfWidth;
  5475. tx2 = tx1 - tl;
  5476. x1 = chartArea.left;
  5477. x2 = chartArea.right;
  5478. }
  5479. const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
  5480. const step = Math.max(1, Math.ceil(ticksLength / limit));
  5481. for (i = 0; i < ticksLength; i += step) {
  5482. const optsAtIndex = grid.setContext(this.getContext(i));
  5483. const lineWidth = optsAtIndex.lineWidth;
  5484. const lineColor = optsAtIndex.color;
  5485. const borderDash = grid.borderDash || [];
  5486. const borderDashOffset = optsAtIndex.borderDashOffset;
  5487. const tickWidth = optsAtIndex.tickWidth;
  5488. const tickColor = optsAtIndex.tickColor;
  5489. const tickBorderDash = optsAtIndex.tickBorderDash || [];
  5490. const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
  5491. lineValue = getPixelForGridLine(this, i, offset);
  5492. if (lineValue === undefined) {
  5493. continue;
  5494. }
  5495. alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
  5496. if (isHorizontal) {
  5497. tx1 = tx2 = x1 = x2 = alignedLineValue;
  5498. } else {
  5499. ty1 = ty2 = y1 = y2 = alignedLineValue;
  5500. }
  5501. items.push({
  5502. tx1,
  5503. ty1,
  5504. tx2,
  5505. ty2,
  5506. x1,
  5507. y1,
  5508. x2,
  5509. y2,
  5510. width: lineWidth,
  5511. color: lineColor,
  5512. borderDash,
  5513. borderDashOffset,
  5514. tickWidth,
  5515. tickColor,
  5516. tickBorderDash,
  5517. tickBorderDashOffset,
  5518. });
  5519. }
  5520. this._ticksLength = ticksLength;
  5521. this._borderValue = borderValue;
  5522. return items;
  5523. }
  5524. _computeLabelItems(chartArea) {
  5525. const axis = this.axis;
  5526. const options = this.options;
  5527. const {position, ticks: optionTicks} = options;
  5528. const isHorizontal = this.isHorizontal();
  5529. const ticks = this.ticks;
  5530. const {align, crossAlign, padding, mirror} = optionTicks;
  5531. const tl = getTickMarkLength(options.grid);
  5532. const tickAndPadding = tl + padding;
  5533. const hTickAndPadding = mirror ? -padding : tickAndPadding;
  5534. const rotation = -toRadians(this.labelRotation);
  5535. const items = [];
  5536. let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
  5537. let textBaseline = 'middle';
  5538. if (position === 'top') {
  5539. y = this.bottom - hTickAndPadding;
  5540. textAlign = this._getXAxisLabelAlignment();
  5541. } else if (position === 'bottom') {
  5542. y = this.top + hTickAndPadding;
  5543. textAlign = this._getXAxisLabelAlignment();
  5544. } else if (position === 'left') {
  5545. const ret = this._getYAxisLabelAlignment(tl);
  5546. textAlign = ret.textAlign;
  5547. x = ret.x;
  5548. } else if (position === 'right') {
  5549. const ret = this._getYAxisLabelAlignment(tl);
  5550. textAlign = ret.textAlign;
  5551. x = ret.x;
  5552. } else if (axis === 'x') {
  5553. if (position === 'center') {
  5554. y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;
  5555. } else if (isObject(position)) {
  5556. const positionAxisID = Object.keys(position)[0];
  5557. const value = position[positionAxisID];
  5558. y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
  5559. }
  5560. textAlign = this._getXAxisLabelAlignment();
  5561. } else if (axis === 'y') {
  5562. if (position === 'center') {
  5563. x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;
  5564. } else if (isObject(position)) {
  5565. const positionAxisID = Object.keys(position)[0];
  5566. const value = position[positionAxisID];
  5567. x = this.chart.scales[positionAxisID].getPixelForValue(value);
  5568. }
  5569. textAlign = this._getYAxisLabelAlignment(tl).textAlign;
  5570. }
  5571. if (axis === 'y') {
  5572. if (align === 'start') {
  5573. textBaseline = 'top';
  5574. } else if (align === 'end') {
  5575. textBaseline = 'bottom';
  5576. }
  5577. }
  5578. const labelSizes = this._getLabelSizes();
  5579. for (i = 0, ilen = ticks.length; i < ilen; ++i) {
  5580. tick = ticks[i];
  5581. label = tick.label;
  5582. const optsAtIndex = optionTicks.setContext(this.getContext(i));
  5583. pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
  5584. font = this._resolveTickFontOptions(i);
  5585. lineHeight = font.lineHeight;
  5586. lineCount = isArray(label) ? label.length : 1;
  5587. const halfCount = lineCount / 2;
  5588. const color = optsAtIndex.color;
  5589. const strokeColor = optsAtIndex.textStrokeColor;
  5590. const strokeWidth = optsAtIndex.textStrokeWidth;
  5591. if (isHorizontal) {
  5592. x = pixel;
  5593. if (position === 'top') {
  5594. if (crossAlign === 'near' || rotation !== 0) {
  5595. textOffset = -lineCount * lineHeight + lineHeight / 2;
  5596. } else if (crossAlign === 'center') {
  5597. textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
  5598. } else {
  5599. textOffset = -labelSizes.highest.height + lineHeight / 2;
  5600. }
  5601. } else {
  5602. if (crossAlign === 'near' || rotation !== 0) {
  5603. textOffset = lineHeight / 2;
  5604. } else if (crossAlign === 'center') {
  5605. textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
  5606. } else {
  5607. textOffset = labelSizes.highest.height - lineCount * lineHeight;
  5608. }
  5609. }
  5610. if (mirror) {
  5611. textOffset *= -1;
  5612. }
  5613. } else {
  5614. y = pixel;
  5615. textOffset = (1 - lineCount) * lineHeight / 2;
  5616. }
  5617. let backdrop;
  5618. if (optsAtIndex.showLabelBackdrop) {
  5619. const labelPadding = toPadding(optsAtIndex.backdropPadding);
  5620. const height = labelSizes.heights[i];
  5621. const width = labelSizes.widths[i];
  5622. let top = y + textOffset - labelPadding.top;
  5623. let left = x - labelPadding.left;
  5624. switch (textBaseline) {
  5625. case 'middle':
  5626. top -= height / 2;
  5627. break;
  5628. case 'bottom':
  5629. top -= height;
  5630. break;
  5631. }
  5632. switch (textAlign) {
  5633. case 'center':
  5634. left -= width / 2;
  5635. break;
  5636. case 'right':
  5637. left -= width;
  5638. break;
  5639. }
  5640. backdrop = {
  5641. left,
  5642. top,
  5643. width: width + labelPadding.width,
  5644. height: height + labelPadding.height,
  5645. color: optsAtIndex.backdropColor,
  5646. };
  5647. }
  5648. items.push({
  5649. rotation,
  5650. label,
  5651. font,
  5652. color,
  5653. strokeColor,
  5654. strokeWidth,
  5655. textOffset,
  5656. textAlign,
  5657. textBaseline,
  5658. translation: [x, y],
  5659. backdrop,
  5660. });
  5661. }
  5662. return items;
  5663. }
  5664. _getXAxisLabelAlignment() {
  5665. const {position, ticks} = this.options;
  5666. const rotation = -toRadians(this.labelRotation);
  5667. if (rotation) {
  5668. return position === 'top' ? 'left' : 'right';
  5669. }
  5670. let align = 'center';
  5671. if (ticks.align === 'start') {
  5672. align = 'left';
  5673. } else if (ticks.align === 'end') {
  5674. align = 'right';
  5675. }
  5676. return align;
  5677. }
  5678. _getYAxisLabelAlignment(tl) {
  5679. const {position, ticks: {crossAlign, mirror, padding}} = this.options;
  5680. const labelSizes = this._getLabelSizes();
  5681. const tickAndPadding = tl + padding;
  5682. const widest = labelSizes.widest.width;
  5683. let textAlign;
  5684. let x;
  5685. if (position === 'left') {
  5686. if (mirror) {
  5687. x = this.right + padding;
  5688. if (crossAlign === 'near') {
  5689. textAlign = 'left';
  5690. } else if (crossAlign === 'center') {
  5691. textAlign = 'center';
  5692. x += (widest / 2);
  5693. } else {
  5694. textAlign = 'right';
  5695. x += widest;
  5696. }
  5697. } else {
  5698. x = this.right - tickAndPadding;
  5699. if (crossAlign === 'near') {
  5700. textAlign = 'right';
  5701. } else if (crossAlign === 'center') {
  5702. textAlign = 'center';
  5703. x -= (widest / 2);
  5704. } else {
  5705. textAlign = 'left';
  5706. x = this.left;
  5707. }
  5708. }
  5709. } else if (position === 'right') {
  5710. if (mirror) {
  5711. x = this.left + padding;
  5712. if (crossAlign === 'near') {
  5713. textAlign = 'right';
  5714. } else if (crossAlign === 'center') {
  5715. textAlign = 'center';
  5716. x -= (widest / 2);
  5717. } else {
  5718. textAlign = 'left';
  5719. x -= widest;
  5720. }
  5721. } else {
  5722. x = this.left + tickAndPadding;
  5723. if (crossAlign === 'near') {
  5724. textAlign = 'left';
  5725. } else if (crossAlign === 'center') {
  5726. textAlign = 'center';
  5727. x += widest / 2;
  5728. } else {
  5729. textAlign = 'right';
  5730. x = this.right;
  5731. }
  5732. }
  5733. } else {
  5734. textAlign = 'right';
  5735. }
  5736. return {textAlign, x};
  5737. }
  5738. _computeLabelArea() {
  5739. if (this.options.ticks.mirror) {
  5740. return;
  5741. }
  5742. const chart = this.chart;
  5743. const position = this.options.position;
  5744. if (position === 'left' || position === 'right') {
  5745. return {top: 0, left: this.left, bottom: chart.height, right: this.right};
  5746. } if (position === 'top' || position === 'bottom') {
  5747. return {top: this.top, left: 0, bottom: this.bottom, right: chart.width};
  5748. }
  5749. }
  5750. drawBackground() {
  5751. const {ctx, options: {backgroundColor}, left, top, width, height} = this;
  5752. if (backgroundColor) {
  5753. ctx.save();
  5754. ctx.fillStyle = backgroundColor;
  5755. ctx.fillRect(left, top, width, height);
  5756. ctx.restore();
  5757. }
  5758. }
  5759. getLineWidthForValue(value) {
  5760. const grid = this.options.grid;
  5761. if (!this._isVisible() || !grid.display) {
  5762. return 0;
  5763. }
  5764. const ticks = this.ticks;
  5765. const index = ticks.findIndex(t => t.value === value);
  5766. if (index >= 0) {
  5767. const opts = grid.setContext(this.getContext(index));
  5768. return opts.lineWidth;
  5769. }
  5770. return 0;
  5771. }
  5772. drawGrid(chartArea) {
  5773. const grid = this.options.grid;
  5774. const ctx = this.ctx;
  5775. const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
  5776. let i, ilen;
  5777. const drawLine = (p1, p2, style) => {
  5778. if (!style.width || !style.color) {
  5779. return;
  5780. }
  5781. ctx.save();
  5782. ctx.lineWidth = style.width;
  5783. ctx.strokeStyle = style.color;
  5784. ctx.setLineDash(style.borderDash || []);
  5785. ctx.lineDashOffset = style.borderDashOffset;
  5786. ctx.beginPath();
  5787. ctx.moveTo(p1.x, p1.y);
  5788. ctx.lineTo(p2.x, p2.y);
  5789. ctx.stroke();
  5790. ctx.restore();
  5791. };
  5792. if (grid.display) {
  5793. for (i = 0, ilen = items.length; i < ilen; ++i) {
  5794. const item = items[i];
  5795. if (grid.drawOnChartArea) {
  5796. drawLine(
  5797. {x: item.x1, y: item.y1},
  5798. {x: item.x2, y: item.y2},
  5799. item
  5800. );
  5801. }
  5802. if (grid.drawTicks) {
  5803. drawLine(
  5804. {x: item.tx1, y: item.ty1},
  5805. {x: item.tx2, y: item.ty2},
  5806. {
  5807. color: item.tickColor,
  5808. width: item.tickWidth,
  5809. borderDash: item.tickBorderDash,
  5810. borderDashOffset: item.tickBorderDashOffset
  5811. }
  5812. );
  5813. }
  5814. }
  5815. }
  5816. }
  5817. drawBorder() {
  5818. const {chart, ctx, options: {grid}} = this;
  5819. const borderOpts = grid.setContext(this.getContext());
  5820. const axisWidth = grid.drawBorder ? borderOpts.borderWidth : 0;
  5821. if (!axisWidth) {
  5822. return;
  5823. }
  5824. const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
  5825. const borderValue = this._borderValue;
  5826. let x1, x2, y1, y2;
  5827. if (this.isHorizontal()) {
  5828. x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
  5829. x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
  5830. y1 = y2 = borderValue;
  5831. } else {
  5832. y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
  5833. y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
  5834. x1 = x2 = borderValue;
  5835. }
  5836. ctx.save();
  5837. ctx.lineWidth = borderOpts.borderWidth;
  5838. ctx.strokeStyle = borderOpts.borderColor;
  5839. ctx.beginPath();
  5840. ctx.moveTo(x1, y1);
  5841. ctx.lineTo(x2, y2);
  5842. ctx.stroke();
  5843. ctx.restore();
  5844. }
  5845. drawLabels(chartArea) {
  5846. const optionTicks = this.options.ticks;
  5847. if (!optionTicks.display) {
  5848. return;
  5849. }
  5850. const ctx = this.ctx;
  5851. const area = this._computeLabelArea();
  5852. if (area) {
  5853. clipArea(ctx, area);
  5854. }
  5855. const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
  5856. let i, ilen;
  5857. for (i = 0, ilen = items.length; i < ilen; ++i) {
  5858. const item = items[i];
  5859. const tickFont = item.font;
  5860. const label = item.label;
  5861. if (item.backdrop) {
  5862. ctx.fillStyle = item.backdrop.color;
  5863. ctx.fillRect(item.backdrop.left, item.backdrop.top, item.backdrop.width, item.backdrop.height);
  5864. }
  5865. let y = item.textOffset;
  5866. renderText(ctx, label, 0, y, tickFont, item);
  5867. }
  5868. if (area) {
  5869. unclipArea(ctx);
  5870. }
  5871. }
  5872. drawTitle() {
  5873. const {ctx, options: {position, title, reverse}} = this;
  5874. if (!title.display) {
  5875. return;
  5876. }
  5877. const font = toFont(title.font);
  5878. const padding = toPadding(title.padding);
  5879. const align = title.align;
  5880. let offset = font.lineHeight / 2;
  5881. if (position === 'bottom' || position === 'center' || isObject(position)) {
  5882. offset += padding.bottom;
  5883. if (isArray(title.text)) {
  5884. offset += font.lineHeight * (title.text.length - 1);
  5885. }
  5886. } else {
  5887. offset += padding.top;
  5888. }
  5889. const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);
  5890. renderText(ctx, title.text, 0, 0, font, {
  5891. color: title.color,
  5892. maxWidth,
  5893. rotation,
  5894. textAlign: titleAlign(align, position, reverse),
  5895. textBaseline: 'middle',
  5896. translation: [titleX, titleY],
  5897. });
  5898. }
  5899. draw(chartArea) {
  5900. if (!this._isVisible()) {
  5901. return;
  5902. }
  5903. this.drawBackground();
  5904. this.drawGrid(chartArea);
  5905. this.drawBorder();
  5906. this.drawTitle();
  5907. this.drawLabels(chartArea);
  5908. }
  5909. _layers() {
  5910. const opts = this.options;
  5911. const tz = opts.ticks && opts.ticks.z || 0;
  5912. const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
  5913. if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
  5914. return [{
  5915. z: tz,
  5916. draw: (chartArea) => {
  5917. this.draw(chartArea);
  5918. }
  5919. }];
  5920. }
  5921. return [{
  5922. z: gz,
  5923. draw: (chartArea) => {
  5924. this.drawBackground();
  5925. this.drawGrid(chartArea);
  5926. this.drawTitle();
  5927. }
  5928. }, {
  5929. z: gz + 1,
  5930. draw: () => {
  5931. this.drawBorder();
  5932. }
  5933. }, {
  5934. z: tz,
  5935. draw: (chartArea) => {
  5936. this.drawLabels(chartArea);
  5937. }
  5938. }];
  5939. }
  5940. getMatchingVisibleMetas(type) {
  5941. const metas = this.chart.getSortedVisibleDatasetMetas();
  5942. const axisID = this.axis + 'AxisID';
  5943. const result = [];
  5944. let i, ilen;
  5945. for (i = 0, ilen = metas.length; i < ilen; ++i) {
  5946. const meta = metas[i];
  5947. if (meta[axisID] === this.id && (!type || meta.type === type)) {
  5948. result.push(meta);
  5949. }
  5950. }
  5951. return result;
  5952. }
  5953. _resolveTickFontOptions(index) {
  5954. const opts = this.options.ticks.setContext(this.getContext(index));
  5955. return toFont(opts.font);
  5956. }
  5957. _maxDigits() {
  5958. const fontSize = this._resolveTickFontOptions(0).lineHeight;
  5959. return (this.isHorizontal() ? this.width : this.height) / fontSize;
  5960. }
  5961. }
  5962. class TypedRegistry {
  5963. constructor(type, scope, override) {
  5964. this.type = type;
  5965. this.scope = scope;
  5966. this.override = override;
  5967. this.items = Object.create(null);
  5968. }
  5969. isForType(type) {
  5970. return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
  5971. }
  5972. register(item) {
  5973. const proto = Object.getPrototypeOf(item);
  5974. let parentScope;
  5975. if (isIChartComponent(proto)) {
  5976. parentScope = this.register(proto);
  5977. }
  5978. const items = this.items;
  5979. const id = item.id;
  5980. const scope = this.scope + '.' + id;
  5981. if (!id) {
  5982. throw new Error('class does not have id: ' + item);
  5983. }
  5984. if (id in items) {
  5985. return scope;
  5986. }
  5987. items[id] = item;
  5988. registerDefaults(item, scope, parentScope);
  5989. if (this.override) {
  5990. defaults.override(item.id, item.overrides);
  5991. }
  5992. return scope;
  5993. }
  5994. get(id) {
  5995. return this.items[id];
  5996. }
  5997. unregister(item) {
  5998. const items = this.items;
  5999. const id = item.id;
  6000. const scope = this.scope;
  6001. if (id in items) {
  6002. delete items[id];
  6003. }
  6004. if (scope && id in defaults[scope]) {
  6005. delete defaults[scope][id];
  6006. if (this.override) {
  6007. delete overrides[id];
  6008. }
  6009. }
  6010. }
  6011. }
  6012. function registerDefaults(item, scope, parentScope) {
  6013. const itemDefaults = merge(Object.create(null), [
  6014. parentScope ? defaults.get(parentScope) : {},
  6015. defaults.get(scope),
  6016. item.defaults
  6017. ]);
  6018. defaults.set(scope, itemDefaults);
  6019. if (item.defaultRoutes) {
  6020. routeDefaults(scope, item.defaultRoutes);
  6021. }
  6022. if (item.descriptors) {
  6023. defaults.describe(scope, item.descriptors);
  6024. }
  6025. }
  6026. function routeDefaults(scope, routes) {
  6027. Object.keys(routes).forEach(property => {
  6028. const propertyParts = property.split('.');
  6029. const sourceName = propertyParts.pop();
  6030. const sourceScope = [scope].concat(propertyParts).join('.');
  6031. const parts = routes[property].split('.');
  6032. const targetName = parts.pop();
  6033. const targetScope = parts.join('.');
  6034. defaults.route(sourceScope, sourceName, targetScope, targetName);
  6035. });
  6036. }
  6037. function isIChartComponent(proto) {
  6038. return 'id' in proto && 'defaults' in proto;
  6039. }
  6040. class Registry {
  6041. constructor() {
  6042. this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
  6043. this.elements = new TypedRegistry(Element, 'elements');
  6044. this.plugins = new TypedRegistry(Object, 'plugins');
  6045. this.scales = new TypedRegistry(Scale, 'scales');
  6046. this._typedRegistries = [this.controllers, this.scales, this.elements];
  6047. }
  6048. add(...args) {
  6049. this._each('register', args);
  6050. }
  6051. remove(...args) {
  6052. this._each('unregister', args);
  6053. }
  6054. addControllers(...args) {
  6055. this._each('register', args, this.controllers);
  6056. }
  6057. addElements(...args) {
  6058. this._each('register', args, this.elements);
  6059. }
  6060. addPlugins(...args) {
  6061. this._each('register', args, this.plugins);
  6062. }
  6063. addScales(...args) {
  6064. this._each('register', args, this.scales);
  6065. }
  6066. getController(id) {
  6067. return this._get(id, this.controllers, 'controller');
  6068. }
  6069. getElement(id) {
  6070. return this._get(id, this.elements, 'element');
  6071. }
  6072. getPlugin(id) {
  6073. return this._get(id, this.plugins, 'plugin');
  6074. }
  6075. getScale(id) {
  6076. return this._get(id, this.scales, 'scale');
  6077. }
  6078. removeControllers(...args) {
  6079. this._each('unregister', args, this.controllers);
  6080. }
  6081. removeElements(...args) {
  6082. this._each('unregister', args, this.elements);
  6083. }
  6084. removePlugins(...args) {
  6085. this._each('unregister', args, this.plugins);
  6086. }
  6087. removeScales(...args) {
  6088. this._each('unregister', args, this.scales);
  6089. }
  6090. _each(method, args, typedRegistry) {
  6091. [...args].forEach(arg => {
  6092. const reg = typedRegistry || this._getRegistryForType(arg);
  6093. if (typedRegistry || reg.isForType(arg) || (reg === this.plugins && arg.id)) {
  6094. this._exec(method, reg, arg);
  6095. } else {
  6096. each(arg, item => {
  6097. const itemReg = typedRegistry || this._getRegistryForType(item);
  6098. this._exec(method, itemReg, item);
  6099. });
  6100. }
  6101. });
  6102. }
  6103. _exec(method, registry, component) {
  6104. const camelMethod = _capitalize(method);
  6105. callback(component['before' + camelMethod], [], component);
  6106. registry[method](component);
  6107. callback(component['after' + camelMethod], [], component);
  6108. }
  6109. _getRegistryForType(type) {
  6110. for (let i = 0; i < this._typedRegistries.length; i++) {
  6111. const reg = this._typedRegistries[i];
  6112. if (reg.isForType(type)) {
  6113. return reg;
  6114. }
  6115. }
  6116. return this.plugins;
  6117. }
  6118. _get(id, typedRegistry, type) {
  6119. const item = typedRegistry.get(id);
  6120. if (item === undefined) {
  6121. throw new Error('"' + id + '" is not a registered ' + type + '.');
  6122. }
  6123. return item;
  6124. }
  6125. }
  6126. var registry = new Registry();
  6127. class PluginService {
  6128. constructor() {
  6129. this._init = [];
  6130. }
  6131. notify(chart, hook, args, filter) {
  6132. if (hook === 'beforeInit') {
  6133. this._init = this._createDescriptors(chart, true);
  6134. this._notify(this._init, chart, 'install');
  6135. }
  6136. const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
  6137. const result = this._notify(descriptors, chart, hook, args);
  6138. if (hook === 'afterDestroy') {
  6139. this._notify(descriptors, chart, 'stop');
  6140. this._notify(this._init, chart, 'uninstall');
  6141. }
  6142. return result;
  6143. }
  6144. _notify(descriptors, chart, hook, args) {
  6145. args = args || {};
  6146. for (const descriptor of descriptors) {
  6147. const plugin = descriptor.plugin;
  6148. const method = plugin[hook];
  6149. const params = [chart, args, descriptor.options];
  6150. if (callback(method, params, plugin) === false && args.cancelable) {
  6151. return false;
  6152. }
  6153. }
  6154. return true;
  6155. }
  6156. invalidate() {
  6157. if (!isNullOrUndef(this._cache)) {
  6158. this._oldCache = this._cache;
  6159. this._cache = undefined;
  6160. }
  6161. }
  6162. _descriptors(chart) {
  6163. if (this._cache) {
  6164. return this._cache;
  6165. }
  6166. const descriptors = this._cache = this._createDescriptors(chart);
  6167. this._notifyStateChanges(chart);
  6168. return descriptors;
  6169. }
  6170. _createDescriptors(chart, all) {
  6171. const config = chart && chart.config;
  6172. const options = valueOrDefault(config.options && config.options.plugins, {});
  6173. const plugins = allPlugins(config);
  6174. return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
  6175. }
  6176. _notifyStateChanges(chart) {
  6177. const previousDescriptors = this._oldCache || [];
  6178. const descriptors = this._cache;
  6179. const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id));
  6180. this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
  6181. this._notify(diff(descriptors, previousDescriptors), chart, 'start');
  6182. }
  6183. }
  6184. function allPlugins(config) {
  6185. const plugins = [];
  6186. const keys = Object.keys(registry.plugins.items);
  6187. for (let i = 0; i < keys.length; i++) {
  6188. plugins.push(registry.getPlugin(keys[i]));
  6189. }
  6190. const local = config.plugins || [];
  6191. for (let i = 0; i < local.length; i++) {
  6192. const plugin = local[i];
  6193. if (plugins.indexOf(plugin) === -1) {
  6194. plugins.push(plugin);
  6195. }
  6196. }
  6197. return plugins;
  6198. }
  6199. function getOpts(options, all) {
  6200. if (!all && options === false) {
  6201. return null;
  6202. }
  6203. if (options === true) {
  6204. return {};
  6205. }
  6206. return options;
  6207. }
  6208. function createDescriptors(chart, plugins, options, all) {
  6209. const result = [];
  6210. const context = chart.getContext();
  6211. for (let i = 0; i < plugins.length; i++) {
  6212. const plugin = plugins[i];
  6213. const id = plugin.id;
  6214. const opts = getOpts(options[id], all);
  6215. if (opts === null) {
  6216. continue;
  6217. }
  6218. result.push({
  6219. plugin,
  6220. options: pluginOpts(chart.config, plugin, opts, context)
  6221. });
  6222. }
  6223. return result;
  6224. }
  6225. function pluginOpts(config, plugin, opts, context) {
  6226. const keys = config.pluginScopeKeys(plugin);
  6227. const scopes = config.getOptionScopes(opts, keys);
  6228. return config.createResolver(scopes, context, [''], {scriptable: false, indexable: false, allKeys: true});
  6229. }
  6230. function getIndexAxis(type, options) {
  6231. const datasetDefaults = defaults.datasets[type] || {};
  6232. const datasetOptions = (options.datasets || {})[type] || {};
  6233. return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  6234. }
  6235. function getAxisFromDefaultScaleID(id, indexAxis) {
  6236. let axis = id;
  6237. if (id === '_index_') {
  6238. axis = indexAxis;
  6239. } else if (id === '_value_') {
  6240. axis = indexAxis === 'x' ? 'y' : 'x';
  6241. }
  6242. return axis;
  6243. }
  6244. function getDefaultScaleIDFromAxis(axis, indexAxis) {
  6245. return axis === indexAxis ? '_index_' : '_value_';
  6246. }
  6247. function axisFromPosition(position) {
  6248. if (position === 'top' || position === 'bottom') {
  6249. return 'x';
  6250. }
  6251. if (position === 'left' || position === 'right') {
  6252. return 'y';
  6253. }
  6254. }
  6255. function determineAxis(id, scaleOptions) {
  6256. if (id === 'x' || id === 'y') {
  6257. return id;
  6258. }
  6259. return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase();
  6260. }
  6261. function mergeScaleConfig(config, options) {
  6262. const chartDefaults = overrides[config.type] || {scales: {}};
  6263. const configScales = options.scales || {};
  6264. const chartIndexAxis = getIndexAxis(config.type, options);
  6265. const firstIDs = Object.create(null);
  6266. const scales = Object.create(null);
  6267. Object.keys(configScales).forEach(id => {
  6268. const scaleConf = configScales[id];
  6269. if (!isObject(scaleConf)) {
  6270. return console.error(`Invalid scale configuration for scale: ${id}`);
  6271. }
  6272. if (scaleConf._proxy) {
  6273. return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
  6274. }
  6275. const axis = determineAxis(id, scaleConf);
  6276. const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
  6277. const defaultScaleOptions = chartDefaults.scales || {};
  6278. firstIDs[axis] = firstIDs[axis] || id;
  6279. scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);
  6280. });
  6281. config.data.datasets.forEach(dataset => {
  6282. const type = dataset.type || config.type;
  6283. const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
  6284. const datasetDefaults = overrides[type] || {};
  6285. const defaultScaleOptions = datasetDefaults.scales || {};
  6286. Object.keys(defaultScaleOptions).forEach(defaultID => {
  6287. const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
  6288. const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis;
  6289. scales[id] = scales[id] || Object.create(null);
  6290. mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);
  6291. });
  6292. });
  6293. Object.keys(scales).forEach(key => {
  6294. const scale = scales[key];
  6295. mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
  6296. });
  6297. return scales;
  6298. }
  6299. function initOptions(config) {
  6300. const options = config.options || (config.options = {});
  6301. options.plugins = valueOrDefault(options.plugins, {});
  6302. options.scales = mergeScaleConfig(config, options);
  6303. }
  6304. function initData(data) {
  6305. data = data || {};
  6306. data.datasets = data.datasets || [];
  6307. data.labels = data.labels || [];
  6308. return data;
  6309. }
  6310. function initConfig(config) {
  6311. config = config || {};
  6312. config.data = initData(config.data);
  6313. initOptions(config);
  6314. return config;
  6315. }
  6316. const keyCache = new Map();
  6317. const keysCached = new Set();
  6318. function cachedKeys(cacheKey, generate) {
  6319. let keys = keyCache.get(cacheKey);
  6320. if (!keys) {
  6321. keys = generate();
  6322. keyCache.set(cacheKey, keys);
  6323. keysCached.add(keys);
  6324. }
  6325. return keys;
  6326. }
  6327. const addIfFound = (set, obj, key) => {
  6328. const opts = resolveObjectKey(obj, key);
  6329. if (opts !== undefined) {
  6330. set.add(opts);
  6331. }
  6332. };
  6333. class Config {
  6334. constructor(config) {
  6335. this._config = initConfig(config);
  6336. this._scopeCache = new Map();
  6337. this._resolverCache = new Map();
  6338. }
  6339. get platform() {
  6340. return this._config.platform;
  6341. }
  6342. get type() {
  6343. return this._config.type;
  6344. }
  6345. set type(type) {
  6346. this._config.type = type;
  6347. }
  6348. get data() {
  6349. return this._config.data;
  6350. }
  6351. set data(data) {
  6352. this._config.data = initData(data);
  6353. }
  6354. get options() {
  6355. return this._config.options;
  6356. }
  6357. set options(options) {
  6358. this._config.options = options;
  6359. }
  6360. get plugins() {
  6361. return this._config.plugins;
  6362. }
  6363. update() {
  6364. const config = this._config;
  6365. this.clearCache();
  6366. initOptions(config);
  6367. }
  6368. clearCache() {
  6369. this._scopeCache.clear();
  6370. this._resolverCache.clear();
  6371. }
  6372. datasetScopeKeys(datasetType) {
  6373. return cachedKeys(datasetType,
  6374. () => [[
  6375. `datasets.${datasetType}`,
  6376. ''
  6377. ]]);
  6378. }
  6379. datasetAnimationScopeKeys(datasetType, transition) {
  6380. return cachedKeys(`${datasetType}.transition.${transition}`,
  6381. () => [
  6382. [
  6383. `datasets.${datasetType}.transitions.${transition}`,
  6384. `transitions.${transition}`,
  6385. ],
  6386. [
  6387. `datasets.${datasetType}`,
  6388. ''
  6389. ]
  6390. ]);
  6391. }
  6392. datasetElementScopeKeys(datasetType, elementType) {
  6393. return cachedKeys(`${datasetType}-${elementType}`,
  6394. () => [[
  6395. `datasets.${datasetType}.elements.${elementType}`,
  6396. `datasets.${datasetType}`,
  6397. `elements.${elementType}`,
  6398. ''
  6399. ]]);
  6400. }
  6401. pluginScopeKeys(plugin) {
  6402. const id = plugin.id;
  6403. const type = this.type;
  6404. return cachedKeys(`${type}-plugin-${id}`,
  6405. () => [[
  6406. `plugins.${id}`,
  6407. ...plugin.additionalOptionScopes || [],
  6408. ]]);
  6409. }
  6410. _cachedScopes(mainScope, resetCache) {
  6411. const _scopeCache = this._scopeCache;
  6412. let cache = _scopeCache.get(mainScope);
  6413. if (!cache || resetCache) {
  6414. cache = new Map();
  6415. _scopeCache.set(mainScope, cache);
  6416. }
  6417. return cache;
  6418. }
  6419. getOptionScopes(mainScope, keyLists, resetCache) {
  6420. const {options, type} = this;
  6421. const cache = this._cachedScopes(mainScope, resetCache);
  6422. const cached = cache.get(keyLists);
  6423. if (cached) {
  6424. return cached;
  6425. }
  6426. const scopes = new Set();
  6427. keyLists.forEach(keys => {
  6428. if (mainScope) {
  6429. scopes.add(mainScope);
  6430. keys.forEach(key => addIfFound(scopes, mainScope, key));
  6431. }
  6432. keys.forEach(key => addIfFound(scopes, options, key));
  6433. keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));
  6434. keys.forEach(key => addIfFound(scopes, defaults, key));
  6435. keys.forEach(key => addIfFound(scopes, descriptors, key));
  6436. });
  6437. const array = Array.from(scopes);
  6438. if (array.length === 0) {
  6439. array.push(Object.create(null));
  6440. }
  6441. if (keysCached.has(keyLists)) {
  6442. cache.set(keyLists, array);
  6443. }
  6444. return array;
  6445. }
  6446. chartOptionScopes() {
  6447. const {options, type} = this;
  6448. return [
  6449. options,
  6450. overrides[type] || {},
  6451. defaults.datasets[type] || {},
  6452. {type},
  6453. defaults,
  6454. descriptors
  6455. ];
  6456. }
  6457. resolveNamedOptions(scopes, names, context, prefixes = ['']) {
  6458. const result = {$shared: true};
  6459. const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
  6460. let options = resolver;
  6461. if (needContext(resolver, names)) {
  6462. result.$shared = false;
  6463. context = isFunction(context) ? context() : context;
  6464. const subResolver = this.createResolver(scopes, context, subPrefixes);
  6465. options = _attachContext(resolver, context, subResolver);
  6466. }
  6467. for (const prop of names) {
  6468. result[prop] = options[prop];
  6469. }
  6470. return result;
  6471. }
  6472. createResolver(scopes, context, prefixes = [''], descriptorDefaults) {
  6473. const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
  6474. return isObject(context)
  6475. ? _attachContext(resolver, context, undefined, descriptorDefaults)
  6476. : resolver;
  6477. }
  6478. }
  6479. function getResolver(resolverCache, scopes, prefixes) {
  6480. let cache = resolverCache.get(scopes);
  6481. if (!cache) {
  6482. cache = new Map();
  6483. resolverCache.set(scopes, cache);
  6484. }
  6485. const cacheKey = prefixes.join();
  6486. let cached = cache.get(cacheKey);
  6487. if (!cached) {
  6488. const resolver = _createResolver(scopes, prefixes);
  6489. cached = {
  6490. resolver,
  6491. subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover'))
  6492. };
  6493. cache.set(cacheKey, cached);
  6494. }
  6495. return cached;
  6496. }
  6497. const hasFunction = value => isObject(value)
  6498. && Object.getOwnPropertyNames(value).reduce((acc, key) => acc || isFunction(value[key]), false);
  6499. function needContext(proxy, names) {
  6500. const {isScriptable, isIndexable} = _descriptors(proxy);
  6501. for (const prop of names) {
  6502. const scriptable = isScriptable(prop);
  6503. const indexable = isIndexable(prop);
  6504. const value = (indexable || scriptable) && proxy[prop];
  6505. if ((scriptable && (isFunction(value) || hasFunction(value)))
  6506. || (indexable && isArray(value))) {
  6507. return true;
  6508. }
  6509. }
  6510. return false;
  6511. }
  6512. var version = "3.7.1";
  6513. const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
  6514. function positionIsHorizontal(position, axis) {
  6515. return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');
  6516. }
  6517. function compare2Level(l1, l2) {
  6518. return function(a, b) {
  6519. return a[l1] === b[l1]
  6520. ? a[l2] - b[l2]
  6521. : a[l1] - b[l1];
  6522. };
  6523. }
  6524. function onAnimationsComplete(context) {
  6525. const chart = context.chart;
  6526. const animationOptions = chart.options.animation;
  6527. chart.notifyPlugins('afterRender');
  6528. callback(animationOptions && animationOptions.onComplete, [context], chart);
  6529. }
  6530. function onAnimationProgress(context) {
  6531. const chart = context.chart;
  6532. const animationOptions = chart.options.animation;
  6533. callback(animationOptions && animationOptions.onProgress, [context], chart);
  6534. }
  6535. function getCanvas(item) {
  6536. if (_isDomSupported() && typeof item === 'string') {
  6537. item = document.getElementById(item);
  6538. } else if (item && item.length) {
  6539. item = item[0];
  6540. }
  6541. if (item && item.canvas) {
  6542. item = item.canvas;
  6543. }
  6544. return item;
  6545. }
  6546. const instances = {};
  6547. const getChart = (key) => {
  6548. const canvas = getCanvas(key);
  6549. return Object.values(instances).filter((c) => c.canvas === canvas).pop();
  6550. };
  6551. function moveNumericKeys(obj, start, move) {
  6552. const keys = Object.keys(obj);
  6553. for (const key of keys) {
  6554. const intKey = +key;
  6555. if (intKey >= start) {
  6556. const value = obj[key];
  6557. delete obj[key];
  6558. if (move > 0 || intKey > start) {
  6559. obj[intKey + move] = value;
  6560. }
  6561. }
  6562. }
  6563. }
  6564. function determineLastEvent(e, lastEvent, inChartArea, isClick) {
  6565. if (!inChartArea || e.type === 'mouseout') {
  6566. return null;
  6567. }
  6568. if (isClick) {
  6569. return lastEvent;
  6570. }
  6571. return e;
  6572. }
  6573. class Chart {
  6574. constructor(item, userConfig) {
  6575. const config = this.config = new Config(userConfig);
  6576. const initialCanvas = getCanvas(item);
  6577. const existingChart = getChart(initialCanvas);
  6578. if (existingChart) {
  6579. throw new Error(
  6580. 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' +
  6581. ' must be destroyed before the canvas can be reused.'
  6582. );
  6583. }
  6584. const options = config.createResolver(config.chartOptionScopes(), this.getContext());
  6585. this.platform = new (config.platform || _detectPlatform(initialCanvas))();
  6586. this.platform.updateConfig(config);
  6587. const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
  6588. const canvas = context && context.canvas;
  6589. const height = canvas && canvas.height;
  6590. const width = canvas && canvas.width;
  6591. this.id = uid();
  6592. this.ctx = context;
  6593. this.canvas = canvas;
  6594. this.width = width;
  6595. this.height = height;
  6596. this._options = options;
  6597. this._aspectRatio = this.aspectRatio;
  6598. this._layers = [];
  6599. this._metasets = [];
  6600. this._stacks = undefined;
  6601. this.boxes = [];
  6602. this.currentDevicePixelRatio = undefined;
  6603. this.chartArea = undefined;
  6604. this._active = [];
  6605. this._lastEvent = undefined;
  6606. this._listeners = {};
  6607. this._responsiveListeners = undefined;
  6608. this._sortedMetasets = [];
  6609. this.scales = {};
  6610. this._plugins = new PluginService();
  6611. this.$proxies = {};
  6612. this._hiddenIndices = {};
  6613. this.attached = false;
  6614. this._animationsDisabled = undefined;
  6615. this.$context = undefined;
  6616. this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0);
  6617. this._dataChanges = [];
  6618. instances[this.id] = this;
  6619. if (!context || !canvas) {
  6620. console.error("Failed to create chart: can't acquire context from the given item");
  6621. return;
  6622. }
  6623. animator.listen(this, 'complete', onAnimationsComplete);
  6624. animator.listen(this, 'progress', onAnimationProgress);
  6625. this._initialize();
  6626. if (this.attached) {
  6627. this.update();
  6628. }
  6629. }
  6630. get aspectRatio() {
  6631. const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this;
  6632. if (!isNullOrUndef(aspectRatio)) {
  6633. return aspectRatio;
  6634. }
  6635. if (maintainAspectRatio && _aspectRatio) {
  6636. return _aspectRatio;
  6637. }
  6638. return height ? width / height : null;
  6639. }
  6640. get data() {
  6641. return this.config.data;
  6642. }
  6643. set data(data) {
  6644. this.config.data = data;
  6645. }
  6646. get options() {
  6647. return this._options;
  6648. }
  6649. set options(options) {
  6650. this.config.options = options;
  6651. }
  6652. _initialize() {
  6653. this.notifyPlugins('beforeInit');
  6654. if (this.options.responsive) {
  6655. this.resize();
  6656. } else {
  6657. retinaScale(this, this.options.devicePixelRatio);
  6658. }
  6659. this.bindEvents();
  6660. this.notifyPlugins('afterInit');
  6661. return this;
  6662. }
  6663. clear() {
  6664. clearCanvas(this.canvas, this.ctx);
  6665. return this;
  6666. }
  6667. stop() {
  6668. animator.stop(this);
  6669. return this;
  6670. }
  6671. resize(width, height) {
  6672. if (!animator.running(this)) {
  6673. this._resize(width, height);
  6674. } else {
  6675. this._resizeBeforeDraw = {width, height};
  6676. }
  6677. }
  6678. _resize(width, height) {
  6679. const options = this.options;
  6680. const canvas = this.canvas;
  6681. const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
  6682. const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
  6683. const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
  6684. const mode = this.width ? 'resize' : 'attach';
  6685. this.width = newSize.width;
  6686. this.height = newSize.height;
  6687. this._aspectRatio = this.aspectRatio;
  6688. if (!retinaScale(this, newRatio, true)) {
  6689. return;
  6690. }
  6691. this.notifyPlugins('resize', {size: newSize});
  6692. callback(options.onResize, [this, newSize], this);
  6693. if (this.attached) {
  6694. if (this._doResize(mode)) {
  6695. this.render();
  6696. }
  6697. }
  6698. }
  6699. ensureScalesHaveIDs() {
  6700. const options = this.options;
  6701. const scalesOptions = options.scales || {};
  6702. each(scalesOptions, (axisOptions, axisID) => {
  6703. axisOptions.id = axisID;
  6704. });
  6705. }
  6706. buildOrUpdateScales() {
  6707. const options = this.options;
  6708. const scaleOpts = options.scales;
  6709. const scales = this.scales;
  6710. const updated = Object.keys(scales).reduce((obj, id) => {
  6711. obj[id] = false;
  6712. return obj;
  6713. }, {});
  6714. let items = [];
  6715. if (scaleOpts) {
  6716. items = items.concat(
  6717. Object.keys(scaleOpts).map((id) => {
  6718. const scaleOptions = scaleOpts[id];
  6719. const axis = determineAxis(id, scaleOptions);
  6720. const isRadial = axis === 'r';
  6721. const isHorizontal = axis === 'x';
  6722. return {
  6723. options: scaleOptions,
  6724. dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
  6725. dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
  6726. };
  6727. })
  6728. );
  6729. }
  6730. each(items, (item) => {
  6731. const scaleOptions = item.options;
  6732. const id = scaleOptions.id;
  6733. const axis = determineAxis(id, scaleOptions);
  6734. const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
  6735. if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
  6736. scaleOptions.position = item.dposition;
  6737. }
  6738. updated[id] = true;
  6739. let scale = null;
  6740. if (id in scales && scales[id].type === scaleType) {
  6741. scale = scales[id];
  6742. } else {
  6743. const scaleClass = registry.getScale(scaleType);
  6744. scale = new scaleClass({
  6745. id,
  6746. type: scaleType,
  6747. ctx: this.ctx,
  6748. chart: this
  6749. });
  6750. scales[scale.id] = scale;
  6751. }
  6752. scale.init(scaleOptions, options);
  6753. });
  6754. each(updated, (hasUpdated, id) => {
  6755. if (!hasUpdated) {
  6756. delete scales[id];
  6757. }
  6758. });
  6759. each(scales, (scale) => {
  6760. layouts.configure(this, scale, scale.options);
  6761. layouts.addBox(this, scale);
  6762. });
  6763. }
  6764. _updateMetasets() {
  6765. const metasets = this._metasets;
  6766. const numData = this.data.datasets.length;
  6767. const numMeta = metasets.length;
  6768. metasets.sort((a, b) => a.index - b.index);
  6769. if (numMeta > numData) {
  6770. for (let i = numData; i < numMeta; ++i) {
  6771. this._destroyDatasetMeta(i);
  6772. }
  6773. metasets.splice(numData, numMeta - numData);
  6774. }
  6775. this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
  6776. }
  6777. _removeUnreferencedMetasets() {
  6778. const {_metasets: metasets, data: {datasets}} = this;
  6779. if (metasets.length > datasets.length) {
  6780. delete this._stacks;
  6781. }
  6782. metasets.forEach((meta, index) => {
  6783. if (datasets.filter(x => x === meta._dataset).length === 0) {
  6784. this._destroyDatasetMeta(index);
  6785. }
  6786. });
  6787. }
  6788. buildOrUpdateControllers() {
  6789. const newControllers = [];
  6790. const datasets = this.data.datasets;
  6791. let i, ilen;
  6792. this._removeUnreferencedMetasets();
  6793. for (i = 0, ilen = datasets.length; i < ilen; i++) {
  6794. const dataset = datasets[i];
  6795. let meta = this.getDatasetMeta(i);
  6796. const type = dataset.type || this.config.type;
  6797. if (meta.type && meta.type !== type) {
  6798. this._destroyDatasetMeta(i);
  6799. meta = this.getDatasetMeta(i);
  6800. }
  6801. meta.type = type;
  6802. meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
  6803. meta.order = dataset.order || 0;
  6804. meta.index = i;
  6805. meta.label = '' + dataset.label;
  6806. meta.visible = this.isDatasetVisible(i);
  6807. if (meta.controller) {
  6808. meta.controller.updateIndex(i);
  6809. meta.controller.linkScales();
  6810. } else {
  6811. const ControllerClass = registry.getController(type);
  6812. const {datasetElementType, dataElementType} = defaults.datasets[type];
  6813. Object.assign(ControllerClass.prototype, {
  6814. dataElementType: registry.getElement(dataElementType),
  6815. datasetElementType: datasetElementType && registry.getElement(datasetElementType)
  6816. });
  6817. meta.controller = new ControllerClass(this, i);
  6818. newControllers.push(meta.controller);
  6819. }
  6820. }
  6821. this._updateMetasets();
  6822. return newControllers;
  6823. }
  6824. _resetElements() {
  6825. each(this.data.datasets, (dataset, datasetIndex) => {
  6826. this.getDatasetMeta(datasetIndex).controller.reset();
  6827. }, this);
  6828. }
  6829. reset() {
  6830. this._resetElements();
  6831. this.notifyPlugins('reset');
  6832. }
  6833. update(mode) {
  6834. const config = this.config;
  6835. config.update();
  6836. const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
  6837. const animsDisabled = this._animationsDisabled = !options.animation;
  6838. this._updateScales();
  6839. this._checkEventBindings();
  6840. this._updateHiddenIndices();
  6841. this._plugins.invalidate();
  6842. if (this.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) {
  6843. return;
  6844. }
  6845. const newControllers = this.buildOrUpdateControllers();
  6846. this.notifyPlugins('beforeElementsUpdate');
  6847. let minPadding = 0;
  6848. for (let i = 0, ilen = this.data.datasets.length; i < ilen; i++) {
  6849. const {controller} = this.getDatasetMeta(i);
  6850. const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
  6851. controller.buildOrUpdateElements(reset);
  6852. minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
  6853. }
  6854. minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
  6855. this._updateLayout(minPadding);
  6856. if (!animsDisabled) {
  6857. each(newControllers, (controller) => {
  6858. controller.reset();
  6859. });
  6860. }
  6861. this._updateDatasets(mode);
  6862. this.notifyPlugins('afterUpdate', {mode});
  6863. this._layers.sort(compare2Level('z', '_idx'));
  6864. const {_active, _lastEvent} = this;
  6865. if (_lastEvent) {
  6866. this._eventHandler(_lastEvent, true);
  6867. } else if (_active.length) {
  6868. this._updateHoverStyles(_active, _active, true);
  6869. }
  6870. this.render();
  6871. }
  6872. _updateScales() {
  6873. each(this.scales, (scale) => {
  6874. layouts.removeBox(this, scale);
  6875. });
  6876. this.ensureScalesHaveIDs();
  6877. this.buildOrUpdateScales();
  6878. }
  6879. _checkEventBindings() {
  6880. const options = this.options;
  6881. const existingEvents = new Set(Object.keys(this._listeners));
  6882. const newEvents = new Set(options.events);
  6883. if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
  6884. this.unbindEvents();
  6885. this.bindEvents();
  6886. }
  6887. }
  6888. _updateHiddenIndices() {
  6889. const {_hiddenIndices} = this;
  6890. const changes = this._getUniformDataChanges() || [];
  6891. for (const {method, start, count} of changes) {
  6892. const move = method === '_removeElements' ? -count : count;
  6893. moveNumericKeys(_hiddenIndices, start, move);
  6894. }
  6895. }
  6896. _getUniformDataChanges() {
  6897. const _dataChanges = this._dataChanges;
  6898. if (!_dataChanges || !_dataChanges.length) {
  6899. return;
  6900. }
  6901. this._dataChanges = [];
  6902. const datasetCount = this.data.datasets.length;
  6903. const makeSet = (idx) => new Set(
  6904. _dataChanges
  6905. .filter(c => c[0] === idx)
  6906. .map((c, i) => i + ',' + c.splice(1).join(','))
  6907. );
  6908. const changeSet = makeSet(0);
  6909. for (let i = 1; i < datasetCount; i++) {
  6910. if (!setsEqual(changeSet, makeSet(i))) {
  6911. return;
  6912. }
  6913. }
  6914. return Array.from(changeSet)
  6915. .map(c => c.split(','))
  6916. .map(a => ({method: a[1], start: +a[2], count: +a[3]}));
  6917. }
  6918. _updateLayout(minPadding) {
  6919. if (this.notifyPlugins('beforeLayout', {cancelable: true}) === false) {
  6920. return;
  6921. }
  6922. layouts.update(this, this.width, this.height, minPadding);
  6923. const area = this.chartArea;
  6924. const noArea = area.width <= 0 || area.height <= 0;
  6925. this._layers = [];
  6926. each(this.boxes, (box) => {
  6927. if (noArea && box.position === 'chartArea') {
  6928. return;
  6929. }
  6930. if (box.configure) {
  6931. box.configure();
  6932. }
  6933. this._layers.push(...box._layers());
  6934. }, this);
  6935. this._layers.forEach((item, index) => {
  6936. item._idx = index;
  6937. });
  6938. this.notifyPlugins('afterLayout');
  6939. }
  6940. _updateDatasets(mode) {
  6941. if (this.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) {
  6942. return;
  6943. }
  6944. for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  6945. this.getDatasetMeta(i).controller.configure();
  6946. }
  6947. for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  6948. this._updateDataset(i, isFunction(mode) ? mode({datasetIndex: i}) : mode);
  6949. }
  6950. this.notifyPlugins('afterDatasetsUpdate', {mode});
  6951. }
  6952. _updateDataset(index, mode) {
  6953. const meta = this.getDatasetMeta(index);
  6954. const args = {meta, index, mode, cancelable: true};
  6955. if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
  6956. return;
  6957. }
  6958. meta.controller._update(mode);
  6959. args.cancelable = false;
  6960. this.notifyPlugins('afterDatasetUpdate', args);
  6961. }
  6962. render() {
  6963. if (this.notifyPlugins('beforeRender', {cancelable: true}) === false) {
  6964. return;
  6965. }
  6966. if (animator.has(this)) {
  6967. if (this.attached && !animator.running(this)) {
  6968. animator.start(this);
  6969. }
  6970. } else {
  6971. this.draw();
  6972. onAnimationsComplete({chart: this});
  6973. }
  6974. }
  6975. draw() {
  6976. let i;
  6977. if (this._resizeBeforeDraw) {
  6978. const {width, height} = this._resizeBeforeDraw;
  6979. this._resize(width, height);
  6980. this._resizeBeforeDraw = null;
  6981. }
  6982. this.clear();
  6983. if (this.width <= 0 || this.height <= 0) {
  6984. return;
  6985. }
  6986. if (this.notifyPlugins('beforeDraw', {cancelable: true}) === false) {
  6987. return;
  6988. }
  6989. const layers = this._layers;
  6990. for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
  6991. layers[i].draw(this.chartArea);
  6992. }
  6993. this._drawDatasets();
  6994. for (; i < layers.length; ++i) {
  6995. layers[i].draw(this.chartArea);
  6996. }
  6997. this.notifyPlugins('afterDraw');
  6998. }
  6999. _getSortedDatasetMetas(filterVisible) {
  7000. const metasets = this._sortedMetasets;
  7001. const result = [];
  7002. let i, ilen;
  7003. for (i = 0, ilen = metasets.length; i < ilen; ++i) {
  7004. const meta = metasets[i];
  7005. if (!filterVisible || meta.visible) {
  7006. result.push(meta);
  7007. }
  7008. }
  7009. return result;
  7010. }
  7011. getSortedVisibleDatasetMetas() {
  7012. return this._getSortedDatasetMetas(true);
  7013. }
  7014. _drawDatasets() {
  7015. if (this.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) {
  7016. return;
  7017. }
  7018. const metasets = this.getSortedVisibleDatasetMetas();
  7019. for (let i = metasets.length - 1; i >= 0; --i) {
  7020. this._drawDataset(metasets[i]);
  7021. }
  7022. this.notifyPlugins('afterDatasetsDraw');
  7023. }
  7024. _drawDataset(meta) {
  7025. const ctx = this.ctx;
  7026. const clip = meta._clip;
  7027. const useClip = !clip.disabled;
  7028. const area = this.chartArea;
  7029. const args = {
  7030. meta,
  7031. index: meta.index,
  7032. cancelable: true
  7033. };
  7034. if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
  7035. return;
  7036. }
  7037. if (useClip) {
  7038. clipArea(ctx, {
  7039. left: clip.left === false ? 0 : area.left - clip.left,
  7040. right: clip.right === false ? this.width : area.right + clip.right,
  7041. top: clip.top === false ? 0 : area.top - clip.top,
  7042. bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
  7043. });
  7044. }
  7045. meta.controller.draw();
  7046. if (useClip) {
  7047. unclipArea(ctx);
  7048. }
  7049. args.cancelable = false;
  7050. this.notifyPlugins('afterDatasetDraw', args);
  7051. }
  7052. getElementsAtEventForMode(e, mode, options, useFinalPosition) {
  7053. const method = Interaction.modes[mode];
  7054. if (typeof method === 'function') {
  7055. return method(this, e, options, useFinalPosition);
  7056. }
  7057. return [];
  7058. }
  7059. getDatasetMeta(datasetIndex) {
  7060. const dataset = this.data.datasets[datasetIndex];
  7061. const metasets = this._metasets;
  7062. let meta = metasets.filter(x => x && x._dataset === dataset).pop();
  7063. if (!meta) {
  7064. meta = {
  7065. type: null,
  7066. data: [],
  7067. dataset: null,
  7068. controller: null,
  7069. hidden: null,
  7070. xAxisID: null,
  7071. yAxisID: null,
  7072. order: dataset && dataset.order || 0,
  7073. index: datasetIndex,
  7074. _dataset: dataset,
  7075. _parsed: [],
  7076. _sorted: false
  7077. };
  7078. metasets.push(meta);
  7079. }
  7080. return meta;
  7081. }
  7082. getContext() {
  7083. return this.$context || (this.$context = createContext(null, {chart: this, type: 'chart'}));
  7084. }
  7085. getVisibleDatasetCount() {
  7086. return this.getSortedVisibleDatasetMetas().length;
  7087. }
  7088. isDatasetVisible(datasetIndex) {
  7089. const dataset = this.data.datasets[datasetIndex];
  7090. if (!dataset) {
  7091. return false;
  7092. }
  7093. const meta = this.getDatasetMeta(datasetIndex);
  7094. return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
  7095. }
  7096. setDatasetVisibility(datasetIndex, visible) {
  7097. const meta = this.getDatasetMeta(datasetIndex);
  7098. meta.hidden = !visible;
  7099. }
  7100. toggleDataVisibility(index) {
  7101. this._hiddenIndices[index] = !this._hiddenIndices[index];
  7102. }
  7103. getDataVisibility(index) {
  7104. return !this._hiddenIndices[index];
  7105. }
  7106. _updateVisibility(datasetIndex, dataIndex, visible) {
  7107. const mode = visible ? 'show' : 'hide';
  7108. const meta = this.getDatasetMeta(datasetIndex);
  7109. const anims = meta.controller._resolveAnimations(undefined, mode);
  7110. if (defined(dataIndex)) {
  7111. meta.data[dataIndex].hidden = !visible;
  7112. this.update();
  7113. } else {
  7114. this.setDatasetVisibility(datasetIndex, visible);
  7115. anims.update(meta, {visible});
  7116. this.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);
  7117. }
  7118. }
  7119. hide(datasetIndex, dataIndex) {
  7120. this._updateVisibility(datasetIndex, dataIndex, false);
  7121. }
  7122. show(datasetIndex, dataIndex) {
  7123. this._updateVisibility(datasetIndex, dataIndex, true);
  7124. }
  7125. _destroyDatasetMeta(datasetIndex) {
  7126. const meta = this._metasets[datasetIndex];
  7127. if (meta && meta.controller) {
  7128. meta.controller._destroy();
  7129. }
  7130. delete this._metasets[datasetIndex];
  7131. }
  7132. _stop() {
  7133. let i, ilen;
  7134. this.stop();
  7135. animator.remove(this);
  7136. for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  7137. this._destroyDatasetMeta(i);
  7138. }
  7139. }
  7140. destroy() {
  7141. this.notifyPlugins('beforeDestroy');
  7142. const {canvas, ctx} = this;
  7143. this._stop();
  7144. this.config.clearCache();
  7145. if (canvas) {
  7146. this.unbindEvents();
  7147. clearCanvas(canvas, ctx);
  7148. this.platform.releaseContext(ctx);
  7149. this.canvas = null;
  7150. this.ctx = null;
  7151. }
  7152. this.notifyPlugins('destroy');
  7153. delete instances[this.id];
  7154. this.notifyPlugins('afterDestroy');
  7155. }
  7156. toBase64Image(...args) {
  7157. return this.canvas.toDataURL(...args);
  7158. }
  7159. bindEvents() {
  7160. this.bindUserEvents();
  7161. if (this.options.responsive) {
  7162. this.bindResponsiveEvents();
  7163. } else {
  7164. this.attached = true;
  7165. }
  7166. }
  7167. bindUserEvents() {
  7168. const listeners = this._listeners;
  7169. const platform = this.platform;
  7170. const _add = (type, listener) => {
  7171. platform.addEventListener(this, type, listener);
  7172. listeners[type] = listener;
  7173. };
  7174. const listener = (e, x, y) => {
  7175. e.offsetX = x;
  7176. e.offsetY = y;
  7177. this._eventHandler(e);
  7178. };
  7179. each(this.options.events, (type) => _add(type, listener));
  7180. }
  7181. bindResponsiveEvents() {
  7182. if (!this._responsiveListeners) {
  7183. this._responsiveListeners = {};
  7184. }
  7185. const listeners = this._responsiveListeners;
  7186. const platform = this.platform;
  7187. const _add = (type, listener) => {
  7188. platform.addEventListener(this, type, listener);
  7189. listeners[type] = listener;
  7190. };
  7191. const _remove = (type, listener) => {
  7192. if (listeners[type]) {
  7193. platform.removeEventListener(this, type, listener);
  7194. delete listeners[type];
  7195. }
  7196. };
  7197. const listener = (width, height) => {
  7198. if (this.canvas) {
  7199. this.resize(width, height);
  7200. }
  7201. };
  7202. let detached;
  7203. const attached = () => {
  7204. _remove('attach', attached);
  7205. this.attached = true;
  7206. this.resize();
  7207. _add('resize', listener);
  7208. _add('detach', detached);
  7209. };
  7210. detached = () => {
  7211. this.attached = false;
  7212. _remove('resize', listener);
  7213. this._stop();
  7214. this._resize(0, 0);
  7215. _add('attach', attached);
  7216. };
  7217. if (platform.isAttached(this.canvas)) {
  7218. attached();
  7219. } else {
  7220. detached();
  7221. }
  7222. }
  7223. unbindEvents() {
  7224. each(this._listeners, (listener, type) => {
  7225. this.platform.removeEventListener(this, type, listener);
  7226. });
  7227. this._listeners = {};
  7228. each(this._responsiveListeners, (listener, type) => {
  7229. this.platform.removeEventListener(this, type, listener);
  7230. });
  7231. this._responsiveListeners = undefined;
  7232. }
  7233. updateHoverStyle(items, mode, enabled) {
  7234. const prefix = enabled ? 'set' : 'remove';
  7235. let meta, item, i, ilen;
  7236. if (mode === 'dataset') {
  7237. meta = this.getDatasetMeta(items[0].datasetIndex);
  7238. meta.controller['_' + prefix + 'DatasetHoverStyle']();
  7239. }
  7240. for (i = 0, ilen = items.length; i < ilen; ++i) {
  7241. item = items[i];
  7242. const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
  7243. if (controller) {
  7244. controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
  7245. }
  7246. }
  7247. }
  7248. getActiveElements() {
  7249. return this._active || [];
  7250. }
  7251. setActiveElements(activeElements) {
  7252. const lastActive = this._active || [];
  7253. const active = activeElements.map(({datasetIndex, index}) => {
  7254. const meta = this.getDatasetMeta(datasetIndex);
  7255. if (!meta) {
  7256. throw new Error('No dataset found at index ' + datasetIndex);
  7257. }
  7258. return {
  7259. datasetIndex,
  7260. element: meta.data[index],
  7261. index,
  7262. };
  7263. });
  7264. const changed = !_elementsEqual(active, lastActive);
  7265. if (changed) {
  7266. this._active = active;
  7267. this._lastEvent = null;
  7268. this._updateHoverStyles(active, lastActive);
  7269. }
  7270. }
  7271. notifyPlugins(hook, args, filter) {
  7272. return this._plugins.notify(this, hook, args, filter);
  7273. }
  7274. _updateHoverStyles(active, lastActive, replay) {
  7275. const hoverOptions = this.options.hover;
  7276. const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index));
  7277. const deactivated = diff(lastActive, active);
  7278. const activated = replay ? active : diff(active, lastActive);
  7279. if (deactivated.length) {
  7280. this.updateHoverStyle(deactivated, hoverOptions.mode, false);
  7281. }
  7282. if (activated.length && hoverOptions.mode) {
  7283. this.updateHoverStyle(activated, hoverOptions.mode, true);
  7284. }
  7285. }
  7286. _eventHandler(e, replay) {
  7287. const args = {
  7288. event: e,
  7289. replay,
  7290. cancelable: true,
  7291. inChartArea: _isPointInArea(e, this.chartArea, this._minPadding)
  7292. };
  7293. const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type);
  7294. if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
  7295. return;
  7296. }
  7297. const changed = this._handleEvent(e, replay, args.inChartArea);
  7298. args.cancelable = false;
  7299. this.notifyPlugins('afterEvent', args, eventFilter);
  7300. if (changed || args.changed) {
  7301. this.render();
  7302. }
  7303. return this;
  7304. }
  7305. _handleEvent(e, replay, inChartArea) {
  7306. const {_active: lastActive = [], options} = this;
  7307. const useFinalPosition = replay;
  7308. const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
  7309. const isClick = _isClickEvent(e);
  7310. const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
  7311. if (inChartArea) {
  7312. this._lastEvent = null;
  7313. callback(options.onHover, [e, active, this], this);
  7314. if (isClick) {
  7315. callback(options.onClick, [e, active, this], this);
  7316. }
  7317. }
  7318. const changed = !_elementsEqual(active, lastActive);
  7319. if (changed || replay) {
  7320. this._active = active;
  7321. this._updateHoverStyles(active, lastActive, replay);
  7322. }
  7323. this._lastEvent = lastEvent;
  7324. return changed;
  7325. }
  7326. _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
  7327. if (e.type === 'mouseout') {
  7328. return [];
  7329. }
  7330. if (!inChartArea) {
  7331. return lastActive;
  7332. }
  7333. const hoverOptions = this.options.hover;
  7334. return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
  7335. }
  7336. }
  7337. const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
  7338. const enumerable = true;
  7339. Object.defineProperties(Chart, {
  7340. defaults: {
  7341. enumerable,
  7342. value: defaults
  7343. },
  7344. instances: {
  7345. enumerable,
  7346. value: instances
  7347. },
  7348. overrides: {
  7349. enumerable,
  7350. value: overrides
  7351. },
  7352. registry: {
  7353. enumerable,
  7354. value: registry
  7355. },
  7356. version: {
  7357. enumerable,
  7358. value: version
  7359. },
  7360. getChart: {
  7361. enumerable,
  7362. value: getChart
  7363. },
  7364. register: {
  7365. enumerable,
  7366. value: (...items) => {
  7367. registry.add(...items);
  7368. invalidatePlugins();
  7369. }
  7370. },
  7371. unregister: {
  7372. enumerable,
  7373. value: (...items) => {
  7374. registry.remove(...items);
  7375. invalidatePlugins();
  7376. }
  7377. }
  7378. });
  7379. function abstract() {
  7380. throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  7381. }
  7382. class DateAdapter {
  7383. constructor(options) {
  7384. this.options = options || {};
  7385. }
  7386. formats() {
  7387. return abstract();
  7388. }
  7389. parse(value, format) {
  7390. return abstract();
  7391. }
  7392. format(timestamp, format) {
  7393. return abstract();
  7394. }
  7395. add(timestamp, amount, unit) {
  7396. return abstract();
  7397. }
  7398. diff(a, b, unit) {
  7399. return abstract();
  7400. }
  7401. startOf(timestamp, unit, weekday) {
  7402. return abstract();
  7403. }
  7404. endOf(timestamp, unit) {
  7405. return abstract();
  7406. }
  7407. }
  7408. DateAdapter.override = function(members) {
  7409. Object.assign(DateAdapter.prototype, members);
  7410. };
  7411. var _adapters = {
  7412. _date: DateAdapter
  7413. };
  7414. function getAllScaleValues(scale, type) {
  7415. if (!scale._cache.$bar) {
  7416. const visibleMetas = scale.getMatchingVisibleMetas(type);
  7417. let values = [];
  7418. for (let i = 0, ilen = visibleMetas.length; i < ilen; i++) {
  7419. values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
  7420. }
  7421. scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b));
  7422. }
  7423. return scale._cache.$bar;
  7424. }
  7425. function computeMinSampleSize(meta) {
  7426. const scale = meta.iScale;
  7427. const values = getAllScaleValues(scale, meta.type);
  7428. let min = scale._length;
  7429. let i, ilen, curr, prev;
  7430. const updateMinAndPrev = () => {
  7431. if (curr === 32767 || curr === -32768) {
  7432. return;
  7433. }
  7434. if (defined(prev)) {
  7435. min = Math.min(min, Math.abs(curr - prev) || min);
  7436. }
  7437. prev = curr;
  7438. };
  7439. for (i = 0, ilen = values.length; i < ilen; ++i) {
  7440. curr = scale.getPixelForValue(values[i]);
  7441. updateMinAndPrev();
  7442. }
  7443. prev = undefined;
  7444. for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {
  7445. curr = scale.getPixelForTick(i);
  7446. updateMinAndPrev();
  7447. }
  7448. return min;
  7449. }
  7450. function computeFitCategoryTraits(index, ruler, options, stackCount) {
  7451. const thickness = options.barThickness;
  7452. let size, ratio;
  7453. if (isNullOrUndef(thickness)) {
  7454. size = ruler.min * options.categoryPercentage;
  7455. ratio = options.barPercentage;
  7456. } else {
  7457. size = thickness * stackCount;
  7458. ratio = 1;
  7459. }
  7460. return {
  7461. chunk: size / stackCount,
  7462. ratio,
  7463. start: ruler.pixels[index] - (size / 2)
  7464. };
  7465. }
  7466. function computeFlexCategoryTraits(index, ruler, options, stackCount) {
  7467. const pixels = ruler.pixels;
  7468. const curr = pixels[index];
  7469. let prev = index > 0 ? pixels[index - 1] : null;
  7470. let next = index < pixels.length - 1 ? pixels[index + 1] : null;
  7471. const percent = options.categoryPercentage;
  7472. if (prev === null) {
  7473. prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
  7474. }
  7475. if (next === null) {
  7476. next = curr + curr - prev;
  7477. }
  7478. const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
  7479. const size = Math.abs(next - prev) / 2 * percent;
  7480. return {
  7481. chunk: size / stackCount,
  7482. ratio: options.barPercentage,
  7483. start
  7484. };
  7485. }
  7486. function parseFloatBar(entry, item, vScale, i) {
  7487. const startValue = vScale.parse(entry[0], i);
  7488. const endValue = vScale.parse(entry[1], i);
  7489. const min = Math.min(startValue, endValue);
  7490. const max = Math.max(startValue, endValue);
  7491. let barStart = min;
  7492. let barEnd = max;
  7493. if (Math.abs(min) > Math.abs(max)) {
  7494. barStart = max;
  7495. barEnd = min;
  7496. }
  7497. item[vScale.axis] = barEnd;
  7498. item._custom = {
  7499. barStart,
  7500. barEnd,
  7501. start: startValue,
  7502. end: endValue,
  7503. min,
  7504. max
  7505. };
  7506. }
  7507. function parseValue(entry, item, vScale, i) {
  7508. if (isArray(entry)) {
  7509. parseFloatBar(entry, item, vScale, i);
  7510. } else {
  7511. item[vScale.axis] = vScale.parse(entry, i);
  7512. }
  7513. return item;
  7514. }
  7515. function parseArrayOrPrimitive(meta, data, start, count) {
  7516. const iScale = meta.iScale;
  7517. const vScale = meta.vScale;
  7518. const labels = iScale.getLabels();
  7519. const singleScale = iScale === vScale;
  7520. const parsed = [];
  7521. let i, ilen, item, entry;
  7522. for (i = start, ilen = start + count; i < ilen; ++i) {
  7523. entry = data[i];
  7524. item = {};
  7525. item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
  7526. parsed.push(parseValue(entry, item, vScale, i));
  7527. }
  7528. return parsed;
  7529. }
  7530. function isFloatBar(custom) {
  7531. return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  7532. }
  7533. function barSign(size, vScale, actualBase) {
  7534. if (size !== 0) {
  7535. return sign(size);
  7536. }
  7537. return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  7538. }
  7539. function borderProps(properties) {
  7540. let reverse, start, end, top, bottom;
  7541. if (properties.horizontal) {
  7542. reverse = properties.base > properties.x;
  7543. start = 'left';
  7544. end = 'right';
  7545. } else {
  7546. reverse = properties.base < properties.y;
  7547. start = 'bottom';
  7548. end = 'top';
  7549. }
  7550. if (reverse) {
  7551. top = 'end';
  7552. bottom = 'start';
  7553. } else {
  7554. top = 'start';
  7555. bottom = 'end';
  7556. }
  7557. return {start, end, reverse, top, bottom};
  7558. }
  7559. function setBorderSkipped(properties, options, stack, index) {
  7560. let edge = options.borderSkipped;
  7561. const res = {};
  7562. if (!edge) {
  7563. properties.borderSkipped = res;
  7564. return;
  7565. }
  7566. const {start, end, reverse, top, bottom} = borderProps(properties);
  7567. if (edge === 'middle' && stack) {
  7568. properties.enableBorderRadius = true;
  7569. if ((stack._top || 0) === index) {
  7570. edge = top;
  7571. } else if ((stack._bottom || 0) === index) {
  7572. edge = bottom;
  7573. } else {
  7574. res[parseEdge(bottom, start, end, reverse)] = true;
  7575. edge = top;
  7576. }
  7577. }
  7578. res[parseEdge(edge, start, end, reverse)] = true;
  7579. properties.borderSkipped = res;
  7580. }
  7581. function parseEdge(edge, a, b, reverse) {
  7582. if (reverse) {
  7583. edge = swap(edge, a, b);
  7584. edge = startEnd(edge, b, a);
  7585. } else {
  7586. edge = startEnd(edge, a, b);
  7587. }
  7588. return edge;
  7589. }
  7590. function swap(orig, v1, v2) {
  7591. return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  7592. }
  7593. function startEnd(v, start, end) {
  7594. return v === 'start' ? start : v === 'end' ? end : v;
  7595. }
  7596. function setInflateAmount(properties, {inflateAmount}, ratio) {
  7597. properties.inflateAmount = inflateAmount === 'auto'
  7598. ? ratio === 1 ? 0.33 : 0
  7599. : inflateAmount;
  7600. }
  7601. class BarController extends DatasetController {
  7602. parsePrimitiveData(meta, data, start, count) {
  7603. return parseArrayOrPrimitive(meta, data, start, count);
  7604. }
  7605. parseArrayData(meta, data, start, count) {
  7606. return parseArrayOrPrimitive(meta, data, start, count);
  7607. }
  7608. parseObjectData(meta, data, start, count) {
  7609. const {iScale, vScale} = meta;
  7610. const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
  7611. const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
  7612. const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
  7613. const parsed = [];
  7614. let i, ilen, item, obj;
  7615. for (i = start, ilen = start + count; i < ilen; ++i) {
  7616. obj = data[i];
  7617. item = {};
  7618. item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
  7619. parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
  7620. }
  7621. return parsed;
  7622. }
  7623. updateRangeFromParsed(range, scale, parsed, stack) {
  7624. super.updateRangeFromParsed(range, scale, parsed, stack);
  7625. const custom = parsed._custom;
  7626. if (custom && scale === this._cachedMeta.vScale) {
  7627. range.min = Math.min(range.min, custom.min);
  7628. range.max = Math.max(range.max, custom.max);
  7629. }
  7630. }
  7631. getMaxOverflow() {
  7632. return 0;
  7633. }
  7634. getLabelAndValue(index) {
  7635. const meta = this._cachedMeta;
  7636. const {iScale, vScale} = meta;
  7637. const parsed = this.getParsed(index);
  7638. const custom = parsed._custom;
  7639. const value = isFloatBar(custom)
  7640. ? '[' + custom.start + ', ' + custom.end + ']'
  7641. : '' + vScale.getLabelForValue(parsed[vScale.axis]);
  7642. return {
  7643. label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
  7644. value
  7645. };
  7646. }
  7647. initialize() {
  7648. this.enableOptionSharing = true;
  7649. super.initialize();
  7650. const meta = this._cachedMeta;
  7651. meta.stack = this.getDataset().stack;
  7652. }
  7653. update(mode) {
  7654. const meta = this._cachedMeta;
  7655. this.updateElements(meta.data, 0, meta.data.length, mode);
  7656. }
  7657. updateElements(bars, start, count, mode) {
  7658. const reset = mode === 'reset';
  7659. const {index, _cachedMeta: {vScale}} = this;
  7660. const base = vScale.getBasePixel();
  7661. const horizontal = vScale.isHorizontal();
  7662. const ruler = this._getRuler();
  7663. const firstOpts = this.resolveDataElementOptions(start, mode);
  7664. const sharedOptions = this.getSharedOptions(firstOpts);
  7665. const includeOptions = this.includeOptions(mode, sharedOptions);
  7666. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  7667. for (let i = start; i < start + count; i++) {
  7668. const parsed = this.getParsed(i);
  7669. const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : this._calculateBarValuePixels(i);
  7670. const ipixels = this._calculateBarIndexPixels(i, ruler);
  7671. const stack = (parsed._stacks || {})[vScale.axis];
  7672. const properties = {
  7673. horizontal,
  7674. base: vpixels.base,
  7675. enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom),
  7676. x: horizontal ? vpixels.head : ipixels.center,
  7677. y: horizontal ? ipixels.center : vpixels.head,
  7678. height: horizontal ? ipixels.size : Math.abs(vpixels.size),
  7679. width: horizontal ? Math.abs(vpixels.size) : ipixels.size
  7680. };
  7681. if (includeOptions) {
  7682. properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
  7683. }
  7684. const options = properties.options || bars[i].options;
  7685. setBorderSkipped(properties, options, stack, index);
  7686. setInflateAmount(properties, options, ruler.ratio);
  7687. this.updateElement(bars[i], i, properties, mode);
  7688. }
  7689. }
  7690. _getStacks(last, dataIndex) {
  7691. const meta = this._cachedMeta;
  7692. const iScale = meta.iScale;
  7693. const metasets = iScale.getMatchingVisibleMetas(this._type);
  7694. const stacked = iScale.options.stacked;
  7695. const ilen = metasets.length;
  7696. const stacks = [];
  7697. let i, item;
  7698. for (i = 0; i < ilen; ++i) {
  7699. item = metasets[i];
  7700. if (!item.controller.options.grouped) {
  7701. continue;
  7702. }
  7703. if (typeof dataIndex !== 'undefined') {
  7704. const val = item.controller.getParsed(dataIndex)[
  7705. item.controller._cachedMeta.vScale.axis
  7706. ];
  7707. if (isNullOrUndef(val) || isNaN(val)) {
  7708. continue;
  7709. }
  7710. }
  7711. if (stacked === false || stacks.indexOf(item.stack) === -1 ||
  7712. (stacked === undefined && item.stack === undefined)) {
  7713. stacks.push(item.stack);
  7714. }
  7715. if (item.index === last) {
  7716. break;
  7717. }
  7718. }
  7719. if (!stacks.length) {
  7720. stacks.push(undefined);
  7721. }
  7722. return stacks;
  7723. }
  7724. _getStackCount(index) {
  7725. return this._getStacks(undefined, index).length;
  7726. }
  7727. _getStackIndex(datasetIndex, name, dataIndex) {
  7728. const stacks = this._getStacks(datasetIndex, dataIndex);
  7729. const index = (name !== undefined)
  7730. ? stacks.indexOf(name)
  7731. : -1;
  7732. return (index === -1)
  7733. ? stacks.length - 1
  7734. : index;
  7735. }
  7736. _getRuler() {
  7737. const opts = this.options;
  7738. const meta = this._cachedMeta;
  7739. const iScale = meta.iScale;
  7740. const pixels = [];
  7741. let i, ilen;
  7742. for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
  7743. pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
  7744. }
  7745. const barThickness = opts.barThickness;
  7746. const min = barThickness || computeMinSampleSize(meta);
  7747. return {
  7748. min,
  7749. pixels,
  7750. start: iScale._startPixel,
  7751. end: iScale._endPixel,
  7752. stackCount: this._getStackCount(),
  7753. scale: iScale,
  7754. grouped: opts.grouped,
  7755. ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
  7756. };
  7757. }
  7758. _calculateBarValuePixels(index) {
  7759. const {_cachedMeta: {vScale, _stacked}, options: {base: baseValue, minBarLength}} = this;
  7760. const actualBase = baseValue || 0;
  7761. const parsed = this.getParsed(index);
  7762. const custom = parsed._custom;
  7763. const floating = isFloatBar(custom);
  7764. let value = parsed[vScale.axis];
  7765. let start = 0;
  7766. let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
  7767. let head, size;
  7768. if (length !== value) {
  7769. start = length - value;
  7770. length = value;
  7771. }
  7772. if (floating) {
  7773. value = custom.barStart;
  7774. length = custom.barEnd - custom.barStart;
  7775. if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
  7776. start = 0;
  7777. }
  7778. start += value;
  7779. }
  7780. const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
  7781. let base = vScale.getPixelForValue(startValue);
  7782. if (this.chart.getDataVisibility(index)) {
  7783. head = vScale.getPixelForValue(start + length);
  7784. } else {
  7785. head = base;
  7786. }
  7787. size = head - base;
  7788. if (Math.abs(size) < minBarLength) {
  7789. size = barSign(size, vScale, actualBase) * minBarLength;
  7790. if (value === actualBase) {
  7791. base -= size / 2;
  7792. }
  7793. head = base + size;
  7794. }
  7795. if (base === vScale.getPixelForValue(actualBase)) {
  7796. const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
  7797. base += halfGrid;
  7798. size -= halfGrid;
  7799. }
  7800. return {
  7801. size,
  7802. base,
  7803. head,
  7804. center: head + size / 2
  7805. };
  7806. }
  7807. _calculateBarIndexPixels(index, ruler) {
  7808. const scale = ruler.scale;
  7809. const options = this.options;
  7810. const skipNull = options.skipNull;
  7811. const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
  7812. let center, size;
  7813. if (ruler.grouped) {
  7814. const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
  7815. const range = options.barThickness === 'flex'
  7816. ? computeFlexCategoryTraits(index, ruler, options, stackCount)
  7817. : computeFitCategoryTraits(index, ruler, options, stackCount);
  7818. const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
  7819. center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
  7820. size = Math.min(maxBarThickness, range.chunk * range.ratio);
  7821. } else {
  7822. center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
  7823. size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
  7824. }
  7825. return {
  7826. base: center - size / 2,
  7827. head: center + size / 2,
  7828. center,
  7829. size
  7830. };
  7831. }
  7832. draw() {
  7833. const meta = this._cachedMeta;
  7834. const vScale = meta.vScale;
  7835. const rects = meta.data;
  7836. const ilen = rects.length;
  7837. let i = 0;
  7838. for (; i < ilen; ++i) {
  7839. if (this.getParsed(i)[vScale.axis] !== null) {
  7840. rects[i].draw(this._ctx);
  7841. }
  7842. }
  7843. }
  7844. }
  7845. BarController.id = 'bar';
  7846. BarController.defaults = {
  7847. datasetElementType: false,
  7848. dataElementType: 'bar',
  7849. categoryPercentage: 0.8,
  7850. barPercentage: 0.9,
  7851. grouped: true,
  7852. animations: {
  7853. numbers: {
  7854. type: 'number',
  7855. properties: ['x', 'y', 'base', 'width', 'height']
  7856. }
  7857. }
  7858. };
  7859. BarController.overrides = {
  7860. scales: {
  7861. _index_: {
  7862. type: 'category',
  7863. offset: true,
  7864. grid: {
  7865. offset: true
  7866. }
  7867. },
  7868. _value_: {
  7869. type: 'linear',
  7870. beginAtZero: true,
  7871. }
  7872. }
  7873. };
  7874. class BubbleController extends DatasetController {
  7875. initialize() {
  7876. this.enableOptionSharing = true;
  7877. super.initialize();
  7878. }
  7879. parsePrimitiveData(meta, data, start, count) {
  7880. const parsed = super.parsePrimitiveData(meta, data, start, count);
  7881. for (let i = 0; i < parsed.length; i++) {
  7882. parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
  7883. }
  7884. return parsed;
  7885. }
  7886. parseArrayData(meta, data, start, count) {
  7887. const parsed = super.parseArrayData(meta, data, start, count);
  7888. for (let i = 0; i < parsed.length; i++) {
  7889. const item = data[start + i];
  7890. parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
  7891. }
  7892. return parsed;
  7893. }
  7894. parseObjectData(meta, data, start, count) {
  7895. const parsed = super.parseObjectData(meta, data, start, count);
  7896. for (let i = 0; i < parsed.length; i++) {
  7897. const item = data[start + i];
  7898. parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
  7899. }
  7900. return parsed;
  7901. }
  7902. getMaxOverflow() {
  7903. const data = this._cachedMeta.data;
  7904. let max = 0;
  7905. for (let i = data.length - 1; i >= 0; --i) {
  7906. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  7907. }
  7908. return max > 0 && max;
  7909. }
  7910. getLabelAndValue(index) {
  7911. const meta = this._cachedMeta;
  7912. const {xScale, yScale} = meta;
  7913. const parsed = this.getParsed(index);
  7914. const x = xScale.getLabelForValue(parsed.x);
  7915. const y = yScale.getLabelForValue(parsed.y);
  7916. const r = parsed._custom;
  7917. return {
  7918. label: meta.label,
  7919. value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
  7920. };
  7921. }
  7922. update(mode) {
  7923. const points = this._cachedMeta.data;
  7924. this.updateElements(points, 0, points.length, mode);
  7925. }
  7926. updateElements(points, start, count, mode) {
  7927. const reset = mode === 'reset';
  7928. const {iScale, vScale} = this._cachedMeta;
  7929. const firstOpts = this.resolveDataElementOptions(start, mode);
  7930. const sharedOptions = this.getSharedOptions(firstOpts);
  7931. const includeOptions = this.includeOptions(mode, sharedOptions);
  7932. const iAxis = iScale.axis;
  7933. const vAxis = vScale.axis;
  7934. for (let i = start; i < start + count; i++) {
  7935. const point = points[i];
  7936. const parsed = !reset && this.getParsed(i);
  7937. const properties = {};
  7938. const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
  7939. const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
  7940. properties.skip = isNaN(iPixel) || isNaN(vPixel);
  7941. if (includeOptions) {
  7942. properties.options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  7943. if (reset) {
  7944. properties.options.radius = 0;
  7945. }
  7946. }
  7947. this.updateElement(point, i, properties, mode);
  7948. }
  7949. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  7950. }
  7951. resolveDataElementOptions(index, mode) {
  7952. const parsed = this.getParsed(index);
  7953. let values = super.resolveDataElementOptions(index, mode);
  7954. if (values.$shared) {
  7955. values = Object.assign({}, values, {$shared: false});
  7956. }
  7957. const radius = values.radius;
  7958. if (mode !== 'active') {
  7959. values.radius = 0;
  7960. }
  7961. values.radius += valueOrDefault(parsed && parsed._custom, radius);
  7962. return values;
  7963. }
  7964. }
  7965. BubbleController.id = 'bubble';
  7966. BubbleController.defaults = {
  7967. datasetElementType: false,
  7968. dataElementType: 'point',
  7969. animations: {
  7970. numbers: {
  7971. type: 'number',
  7972. properties: ['x', 'y', 'borderWidth', 'radius']
  7973. }
  7974. }
  7975. };
  7976. BubbleController.overrides = {
  7977. scales: {
  7978. x: {
  7979. type: 'linear'
  7980. },
  7981. y: {
  7982. type: 'linear'
  7983. }
  7984. },
  7985. plugins: {
  7986. tooltip: {
  7987. callbacks: {
  7988. title() {
  7989. return '';
  7990. }
  7991. }
  7992. }
  7993. }
  7994. };
  7995. function getRatioAndOffset(rotation, circumference, cutout) {
  7996. let ratioX = 1;
  7997. let ratioY = 1;
  7998. let offsetX = 0;
  7999. let offsetY = 0;
  8000. if (circumference < TAU) {
  8001. const startAngle = rotation;
  8002. const endAngle = startAngle + circumference;
  8003. const startX = Math.cos(startAngle);
  8004. const startY = Math.sin(startAngle);
  8005. const endX = Math.cos(endAngle);
  8006. const endY = Math.sin(endAngle);
  8007. const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
  8008. const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
  8009. const maxX = calcMax(0, startX, endX);
  8010. const maxY = calcMax(HALF_PI, startY, endY);
  8011. const minX = calcMin(PI, startX, endX);
  8012. const minY = calcMin(PI + HALF_PI, startY, endY);
  8013. ratioX = (maxX - minX) / 2;
  8014. ratioY = (maxY - minY) / 2;
  8015. offsetX = -(maxX + minX) / 2;
  8016. offsetY = -(maxY + minY) / 2;
  8017. }
  8018. return {ratioX, ratioY, offsetX, offsetY};
  8019. }
  8020. class DoughnutController extends DatasetController {
  8021. constructor(chart, datasetIndex) {
  8022. super(chart, datasetIndex);
  8023. this.enableOptionSharing = true;
  8024. this.innerRadius = undefined;
  8025. this.outerRadius = undefined;
  8026. this.offsetX = undefined;
  8027. this.offsetY = undefined;
  8028. }
  8029. linkScales() {}
  8030. parse(start, count) {
  8031. const data = this.getDataset().data;
  8032. const meta = this._cachedMeta;
  8033. if (this._parsing === false) {
  8034. meta._parsed = data;
  8035. } else {
  8036. let getter = (i) => +data[i];
  8037. if (isObject(data[start])) {
  8038. const {key = 'value'} = this._parsing;
  8039. getter = (i) => +resolveObjectKey(data[i], key);
  8040. }
  8041. let i, ilen;
  8042. for (i = start, ilen = start + count; i < ilen; ++i) {
  8043. meta._parsed[i] = getter(i);
  8044. }
  8045. }
  8046. }
  8047. _getRotation() {
  8048. return toRadians(this.options.rotation - 90);
  8049. }
  8050. _getCircumference() {
  8051. return toRadians(this.options.circumference);
  8052. }
  8053. _getRotationExtents() {
  8054. let min = TAU;
  8055. let max = -TAU;
  8056. for (let i = 0; i < this.chart.data.datasets.length; ++i) {
  8057. if (this.chart.isDatasetVisible(i)) {
  8058. const controller = this.chart.getDatasetMeta(i).controller;
  8059. const rotation = controller._getRotation();
  8060. const circumference = controller._getCircumference();
  8061. min = Math.min(min, rotation);
  8062. max = Math.max(max, rotation + circumference);
  8063. }
  8064. }
  8065. return {
  8066. rotation: min,
  8067. circumference: max - min,
  8068. };
  8069. }
  8070. update(mode) {
  8071. const chart = this.chart;
  8072. const {chartArea} = chart;
  8073. const meta = this._cachedMeta;
  8074. const arcs = meta.data;
  8075. const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
  8076. const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
  8077. const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
  8078. const chartWeight = this._getRingWeight(this.index);
  8079. const {circumference, rotation} = this._getRotationExtents();
  8080. const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);
  8081. const maxWidth = (chartArea.width - spacing) / ratioX;
  8082. const maxHeight = (chartArea.height - spacing) / ratioY;
  8083. const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
  8084. const outerRadius = toDimension(this.options.radius, maxRadius);
  8085. const innerRadius = Math.max(outerRadius * cutout, 0);
  8086. const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
  8087. this.offsetX = offsetX * outerRadius;
  8088. this.offsetY = offsetY * outerRadius;
  8089. meta.total = this.calculateTotal();
  8090. this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
  8091. this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
  8092. this.updateElements(arcs, 0, arcs.length, mode);
  8093. }
  8094. _circumference(i, reset) {
  8095. const opts = this.options;
  8096. const meta = this._cachedMeta;
  8097. const circumference = this._getCircumference();
  8098. if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
  8099. return 0;
  8100. }
  8101. return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
  8102. }
  8103. updateElements(arcs, start, count, mode) {
  8104. const reset = mode === 'reset';
  8105. const chart = this.chart;
  8106. const chartArea = chart.chartArea;
  8107. const opts = chart.options;
  8108. const animationOpts = opts.animation;
  8109. const centerX = (chartArea.left + chartArea.right) / 2;
  8110. const centerY = (chartArea.top + chartArea.bottom) / 2;
  8111. const animateScale = reset && animationOpts.animateScale;
  8112. const innerRadius = animateScale ? 0 : this.innerRadius;
  8113. const outerRadius = animateScale ? 0 : this.outerRadius;
  8114. const firstOpts = this.resolveDataElementOptions(start, mode);
  8115. const sharedOptions = this.getSharedOptions(firstOpts);
  8116. const includeOptions = this.includeOptions(mode, sharedOptions);
  8117. let startAngle = this._getRotation();
  8118. let i;
  8119. for (i = 0; i < start; ++i) {
  8120. startAngle += this._circumference(i, reset);
  8121. }
  8122. for (i = start; i < start + count; ++i) {
  8123. const circumference = this._circumference(i, reset);
  8124. const arc = arcs[i];
  8125. const properties = {
  8126. x: centerX + this.offsetX,
  8127. y: centerY + this.offsetY,
  8128. startAngle,
  8129. endAngle: startAngle + circumference,
  8130. circumference,
  8131. outerRadius,
  8132. innerRadius
  8133. };
  8134. if (includeOptions) {
  8135. properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
  8136. }
  8137. startAngle += circumference;
  8138. this.updateElement(arc, i, properties, mode);
  8139. }
  8140. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  8141. }
  8142. calculateTotal() {
  8143. const meta = this._cachedMeta;
  8144. const metaData = meta.data;
  8145. let total = 0;
  8146. let i;
  8147. for (i = 0; i < metaData.length; i++) {
  8148. const value = meta._parsed[i];
  8149. if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
  8150. total += Math.abs(value);
  8151. }
  8152. }
  8153. return total;
  8154. }
  8155. calculateCircumference(value) {
  8156. const total = this._cachedMeta.total;
  8157. if (total > 0 && !isNaN(value)) {
  8158. return TAU * (Math.abs(value) / total);
  8159. }
  8160. return 0;
  8161. }
  8162. getLabelAndValue(index) {
  8163. const meta = this._cachedMeta;
  8164. const chart = this.chart;
  8165. const labels = chart.data.labels || [];
  8166. const value = formatNumber(meta._parsed[index], chart.options.locale);
  8167. return {
  8168. label: labels[index] || '',
  8169. value,
  8170. };
  8171. }
  8172. getMaxBorderWidth(arcs) {
  8173. let max = 0;
  8174. const chart = this.chart;
  8175. let i, ilen, meta, controller, options;
  8176. if (!arcs) {
  8177. for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
  8178. if (chart.isDatasetVisible(i)) {
  8179. meta = chart.getDatasetMeta(i);
  8180. arcs = meta.data;
  8181. controller = meta.controller;
  8182. break;
  8183. }
  8184. }
  8185. }
  8186. if (!arcs) {
  8187. return 0;
  8188. }
  8189. for (i = 0, ilen = arcs.length; i < ilen; ++i) {
  8190. options = controller.resolveDataElementOptions(i);
  8191. if (options.borderAlign !== 'inner') {
  8192. max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
  8193. }
  8194. }
  8195. return max;
  8196. }
  8197. getMaxOffset(arcs) {
  8198. let max = 0;
  8199. for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
  8200. const options = this.resolveDataElementOptions(i);
  8201. max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
  8202. }
  8203. return max;
  8204. }
  8205. _getRingWeightOffset(datasetIndex) {
  8206. let ringWeightOffset = 0;
  8207. for (let i = 0; i < datasetIndex; ++i) {
  8208. if (this.chart.isDatasetVisible(i)) {
  8209. ringWeightOffset += this._getRingWeight(i);
  8210. }
  8211. }
  8212. return ringWeightOffset;
  8213. }
  8214. _getRingWeight(datasetIndex) {
  8215. return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
  8216. }
  8217. _getVisibleDatasetWeightTotal() {
  8218. return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
  8219. }
  8220. }
  8221. DoughnutController.id = 'doughnut';
  8222. DoughnutController.defaults = {
  8223. datasetElementType: false,
  8224. dataElementType: 'arc',
  8225. animation: {
  8226. animateRotate: true,
  8227. animateScale: false
  8228. },
  8229. animations: {
  8230. numbers: {
  8231. type: 'number',
  8232. properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing']
  8233. },
  8234. },
  8235. cutout: '50%',
  8236. rotation: 0,
  8237. circumference: 360,
  8238. radius: '100%',
  8239. spacing: 0,
  8240. indexAxis: 'r',
  8241. };
  8242. DoughnutController.descriptors = {
  8243. _scriptable: (name) => name !== 'spacing',
  8244. _indexable: (name) => name !== 'spacing',
  8245. };
  8246. DoughnutController.overrides = {
  8247. aspectRatio: 1,
  8248. plugins: {
  8249. legend: {
  8250. labels: {
  8251. generateLabels(chart) {
  8252. const data = chart.data;
  8253. if (data.labels.length && data.datasets.length) {
  8254. const {labels: {pointStyle}} = chart.legend.options;
  8255. return data.labels.map((label, i) => {
  8256. const meta = chart.getDatasetMeta(0);
  8257. const style = meta.controller.getStyle(i);
  8258. return {
  8259. text: label,
  8260. fillStyle: style.backgroundColor,
  8261. strokeStyle: style.borderColor,
  8262. lineWidth: style.borderWidth,
  8263. pointStyle: pointStyle,
  8264. hidden: !chart.getDataVisibility(i),
  8265. index: i
  8266. };
  8267. });
  8268. }
  8269. return [];
  8270. }
  8271. },
  8272. onClick(e, legendItem, legend) {
  8273. legend.chart.toggleDataVisibility(legendItem.index);
  8274. legend.chart.update();
  8275. }
  8276. },
  8277. tooltip: {
  8278. callbacks: {
  8279. title() {
  8280. return '';
  8281. },
  8282. label(tooltipItem) {
  8283. let dataLabel = tooltipItem.label;
  8284. const value = ': ' + tooltipItem.formattedValue;
  8285. if (isArray(dataLabel)) {
  8286. dataLabel = dataLabel.slice();
  8287. dataLabel[0] += value;
  8288. } else {
  8289. dataLabel += value;
  8290. }
  8291. return dataLabel;
  8292. }
  8293. }
  8294. }
  8295. }
  8296. };
  8297. class LineController extends DatasetController {
  8298. initialize() {
  8299. this.enableOptionSharing = true;
  8300. super.initialize();
  8301. }
  8302. update(mode) {
  8303. const meta = this._cachedMeta;
  8304. const {dataset: line, data: points = [], _dataset} = meta;
  8305. const animationsDisabled = this.chart._animationsDisabled;
  8306. let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  8307. this._drawStart = start;
  8308. this._drawCount = count;
  8309. if (scaleRangesChanged(meta)) {
  8310. start = 0;
  8311. count = points.length;
  8312. }
  8313. line._chart = this.chart;
  8314. line._datasetIndex = this.index;
  8315. line._decimated = !!_dataset._decimated;
  8316. line.points = points;
  8317. const options = this.resolveDatasetElementOptions(mode);
  8318. if (!this.options.showLine) {
  8319. options.borderWidth = 0;
  8320. }
  8321. options.segment = this.options.segment;
  8322. this.updateElement(line, undefined, {
  8323. animated: !animationsDisabled,
  8324. options
  8325. }, mode);
  8326. this.updateElements(points, start, count, mode);
  8327. }
  8328. updateElements(points, start, count, mode) {
  8329. const reset = mode === 'reset';
  8330. const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;
  8331. const firstOpts = this.resolveDataElementOptions(start, mode);
  8332. const sharedOptions = this.getSharedOptions(firstOpts);
  8333. const includeOptions = this.includeOptions(mode, sharedOptions);
  8334. const iAxis = iScale.axis;
  8335. const vAxis = vScale.axis;
  8336. const {spanGaps, segment} = this.options;
  8337. const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  8338. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  8339. let prevParsed = start > 0 && this.getParsed(start - 1);
  8340. for (let i = start; i < start + count; ++i) {
  8341. const point = points[i];
  8342. const parsed = this.getParsed(i);
  8343. const properties = directUpdate ? point : {};
  8344. const nullData = isNullOrUndef(parsed[vAxis]);
  8345. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  8346. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  8347. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  8348. properties.stop = i > 0 && (parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  8349. if (segment) {
  8350. properties.parsed = parsed;
  8351. properties.raw = _dataset.data[i];
  8352. }
  8353. if (includeOptions) {
  8354. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  8355. }
  8356. if (!directUpdate) {
  8357. this.updateElement(point, i, properties, mode);
  8358. }
  8359. prevParsed = parsed;
  8360. }
  8361. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  8362. }
  8363. getMaxOverflow() {
  8364. const meta = this._cachedMeta;
  8365. const dataset = meta.dataset;
  8366. const border = dataset.options && dataset.options.borderWidth || 0;
  8367. const data = meta.data || [];
  8368. if (!data.length) {
  8369. return border;
  8370. }
  8371. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  8372. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  8373. return Math.max(border, firstPoint, lastPoint) / 2;
  8374. }
  8375. draw() {
  8376. const meta = this._cachedMeta;
  8377. meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
  8378. super.draw();
  8379. }
  8380. }
  8381. LineController.id = 'line';
  8382. LineController.defaults = {
  8383. datasetElementType: 'line',
  8384. dataElementType: 'point',
  8385. showLine: true,
  8386. spanGaps: false,
  8387. };
  8388. LineController.overrides = {
  8389. scales: {
  8390. _index_: {
  8391. type: 'category',
  8392. },
  8393. _value_: {
  8394. type: 'linear',
  8395. },
  8396. }
  8397. };
  8398. function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
  8399. const pointCount = points.length;
  8400. let start = 0;
  8401. let count = pointCount;
  8402. if (meta._sorted) {
  8403. const {iScale, _parsed} = meta;
  8404. const axis = iScale.axis;
  8405. const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
  8406. if (minDefined) {
  8407. start = _limitValue(Math.min(
  8408. _lookupByKey(_parsed, iScale.axis, min).lo,
  8409. animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
  8410. 0, pointCount - 1);
  8411. }
  8412. if (maxDefined) {
  8413. count = _limitValue(Math.max(
  8414. _lookupByKey(_parsed, iScale.axis, max).hi + 1,
  8415. animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1),
  8416. start, pointCount) - start;
  8417. } else {
  8418. count = pointCount - start;
  8419. }
  8420. }
  8421. return {start, count};
  8422. }
  8423. function scaleRangesChanged(meta) {
  8424. const {xScale, yScale, _scaleRanges} = meta;
  8425. const newRanges = {
  8426. xmin: xScale.min,
  8427. xmax: xScale.max,
  8428. ymin: yScale.min,
  8429. ymax: yScale.max
  8430. };
  8431. if (!_scaleRanges) {
  8432. meta._scaleRanges = newRanges;
  8433. return true;
  8434. }
  8435. const changed = _scaleRanges.xmin !== xScale.min
  8436. || _scaleRanges.xmax !== xScale.max
  8437. || _scaleRanges.ymin !== yScale.min
  8438. || _scaleRanges.ymax !== yScale.max;
  8439. Object.assign(_scaleRanges, newRanges);
  8440. return changed;
  8441. }
  8442. class PolarAreaController extends DatasetController {
  8443. constructor(chart, datasetIndex) {
  8444. super(chart, datasetIndex);
  8445. this.innerRadius = undefined;
  8446. this.outerRadius = undefined;
  8447. }
  8448. getLabelAndValue(index) {
  8449. const meta = this._cachedMeta;
  8450. const chart = this.chart;
  8451. const labels = chart.data.labels || [];
  8452. const value = formatNumber(meta._parsed[index].r, chart.options.locale);
  8453. return {
  8454. label: labels[index] || '',
  8455. value,
  8456. };
  8457. }
  8458. update(mode) {
  8459. const arcs = this._cachedMeta.data;
  8460. this._updateRadius();
  8461. this.updateElements(arcs, 0, arcs.length, mode);
  8462. }
  8463. _updateRadius() {
  8464. const chart = this.chart;
  8465. const chartArea = chart.chartArea;
  8466. const opts = chart.options;
  8467. const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  8468. const outerRadius = Math.max(minSize / 2, 0);
  8469. const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
  8470. const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
  8471. this.outerRadius = outerRadius - (radiusLength * this.index);
  8472. this.innerRadius = this.outerRadius - radiusLength;
  8473. }
  8474. updateElements(arcs, start, count, mode) {
  8475. const reset = mode === 'reset';
  8476. const chart = this.chart;
  8477. const dataset = this.getDataset();
  8478. const opts = chart.options;
  8479. const animationOpts = opts.animation;
  8480. const scale = this._cachedMeta.rScale;
  8481. const centerX = scale.xCenter;
  8482. const centerY = scale.yCenter;
  8483. const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
  8484. let angle = datasetStartAngle;
  8485. let i;
  8486. const defaultAngle = 360 / this.countVisibleElements();
  8487. for (i = 0; i < start; ++i) {
  8488. angle += this._computeAngle(i, mode, defaultAngle);
  8489. }
  8490. for (i = start; i < start + count; i++) {
  8491. const arc = arcs[i];
  8492. let startAngle = angle;
  8493. let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
  8494. let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0;
  8495. angle = endAngle;
  8496. if (reset) {
  8497. if (animationOpts.animateScale) {
  8498. outerRadius = 0;
  8499. }
  8500. if (animationOpts.animateRotate) {
  8501. startAngle = endAngle = datasetStartAngle;
  8502. }
  8503. }
  8504. const properties = {
  8505. x: centerX,
  8506. y: centerY,
  8507. innerRadius: 0,
  8508. outerRadius,
  8509. startAngle,
  8510. endAngle,
  8511. options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
  8512. };
  8513. this.updateElement(arc, i, properties, mode);
  8514. }
  8515. }
  8516. countVisibleElements() {
  8517. const dataset = this.getDataset();
  8518. const meta = this._cachedMeta;
  8519. let count = 0;
  8520. meta.data.forEach((element, index) => {
  8521. if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) {
  8522. count++;
  8523. }
  8524. });
  8525. return count;
  8526. }
  8527. _computeAngle(index, mode, defaultAngle) {
  8528. return this.chart.getDataVisibility(index)
  8529. ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle)
  8530. : 0;
  8531. }
  8532. }
  8533. PolarAreaController.id = 'polarArea';
  8534. PolarAreaController.defaults = {
  8535. dataElementType: 'arc',
  8536. animation: {
  8537. animateRotate: true,
  8538. animateScale: true
  8539. },
  8540. animations: {
  8541. numbers: {
  8542. type: 'number',
  8543. properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
  8544. },
  8545. },
  8546. indexAxis: 'r',
  8547. startAngle: 0,
  8548. };
  8549. PolarAreaController.overrides = {
  8550. aspectRatio: 1,
  8551. plugins: {
  8552. legend: {
  8553. labels: {
  8554. generateLabels(chart) {
  8555. const data = chart.data;
  8556. if (data.labels.length && data.datasets.length) {
  8557. const {labels: {pointStyle}} = chart.legend.options;
  8558. return data.labels.map((label, i) => {
  8559. const meta = chart.getDatasetMeta(0);
  8560. const style = meta.controller.getStyle(i);
  8561. return {
  8562. text: label,
  8563. fillStyle: style.backgroundColor,
  8564. strokeStyle: style.borderColor,
  8565. lineWidth: style.borderWidth,
  8566. pointStyle: pointStyle,
  8567. hidden: !chart.getDataVisibility(i),
  8568. index: i
  8569. };
  8570. });
  8571. }
  8572. return [];
  8573. }
  8574. },
  8575. onClick(e, legendItem, legend) {
  8576. legend.chart.toggleDataVisibility(legendItem.index);
  8577. legend.chart.update();
  8578. }
  8579. },
  8580. tooltip: {
  8581. callbacks: {
  8582. title() {
  8583. return '';
  8584. },
  8585. label(context) {
  8586. return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue;
  8587. }
  8588. }
  8589. }
  8590. },
  8591. scales: {
  8592. r: {
  8593. type: 'radialLinear',
  8594. angleLines: {
  8595. display: false
  8596. },
  8597. beginAtZero: true,
  8598. grid: {
  8599. circular: true
  8600. },
  8601. pointLabels: {
  8602. display: false
  8603. },
  8604. startAngle: 0
  8605. }
  8606. }
  8607. };
  8608. class PieController extends DoughnutController {
  8609. }
  8610. PieController.id = 'pie';
  8611. PieController.defaults = {
  8612. cutout: 0,
  8613. rotation: 0,
  8614. circumference: 360,
  8615. radius: '100%'
  8616. };
  8617. class RadarController extends DatasetController {
  8618. getLabelAndValue(index) {
  8619. const vScale = this._cachedMeta.vScale;
  8620. const parsed = this.getParsed(index);
  8621. return {
  8622. label: vScale.getLabels()[index],
  8623. value: '' + vScale.getLabelForValue(parsed[vScale.axis])
  8624. };
  8625. }
  8626. update(mode) {
  8627. const meta = this._cachedMeta;
  8628. const line = meta.dataset;
  8629. const points = meta.data || [];
  8630. const labels = meta.iScale.getLabels();
  8631. line.points = points;
  8632. if (mode !== 'resize') {
  8633. const options = this.resolveDatasetElementOptions(mode);
  8634. if (!this.options.showLine) {
  8635. options.borderWidth = 0;
  8636. }
  8637. const properties = {
  8638. _loop: true,
  8639. _fullLoop: labels.length === points.length,
  8640. options
  8641. };
  8642. this.updateElement(line, undefined, properties, mode);
  8643. }
  8644. this.updateElements(points, 0, points.length, mode);
  8645. }
  8646. updateElements(points, start, count, mode) {
  8647. const dataset = this.getDataset();
  8648. const scale = this._cachedMeta.rScale;
  8649. const reset = mode === 'reset';
  8650. for (let i = start; i < start + count; i++) {
  8651. const point = points[i];
  8652. const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  8653. const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
  8654. const x = reset ? scale.xCenter : pointPosition.x;
  8655. const y = reset ? scale.yCenter : pointPosition.y;
  8656. const properties = {
  8657. x,
  8658. y,
  8659. angle: pointPosition.angle,
  8660. skip: isNaN(x) || isNaN(y),
  8661. options
  8662. };
  8663. this.updateElement(point, i, properties, mode);
  8664. }
  8665. }
  8666. }
  8667. RadarController.id = 'radar';
  8668. RadarController.defaults = {
  8669. datasetElementType: 'line',
  8670. dataElementType: 'point',
  8671. indexAxis: 'r',
  8672. showLine: true,
  8673. elements: {
  8674. line: {
  8675. fill: 'start'
  8676. }
  8677. },
  8678. };
  8679. RadarController.overrides = {
  8680. aspectRatio: 1,
  8681. scales: {
  8682. r: {
  8683. type: 'radialLinear',
  8684. }
  8685. }
  8686. };
  8687. class ScatterController extends LineController {
  8688. }
  8689. ScatterController.id = 'scatter';
  8690. ScatterController.defaults = {
  8691. showLine: false,
  8692. fill: false
  8693. };
  8694. ScatterController.overrides = {
  8695. interaction: {
  8696. mode: 'point'
  8697. },
  8698. plugins: {
  8699. tooltip: {
  8700. callbacks: {
  8701. title() {
  8702. return '';
  8703. },
  8704. label(item) {
  8705. return '(' + item.label + ', ' + item.formattedValue + ')';
  8706. }
  8707. }
  8708. }
  8709. },
  8710. scales: {
  8711. x: {
  8712. type: 'linear'
  8713. },
  8714. y: {
  8715. type: 'linear'
  8716. }
  8717. }
  8718. };
  8719. var controllers = /*#__PURE__*/Object.freeze({
  8720. __proto__: null,
  8721. BarController: BarController,
  8722. BubbleController: BubbleController,
  8723. DoughnutController: DoughnutController,
  8724. LineController: LineController,
  8725. PolarAreaController: PolarAreaController,
  8726. PieController: PieController,
  8727. RadarController: RadarController,
  8728. ScatterController: ScatterController
  8729. });
  8730. function clipArc(ctx, element, endAngle) {
  8731. const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
  8732. let angleMargin = pixelMargin / outerRadius;
  8733. ctx.beginPath();
  8734. ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
  8735. if (innerRadius > pixelMargin) {
  8736. angleMargin = pixelMargin / innerRadius;
  8737. ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
  8738. } else {
  8739. ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
  8740. }
  8741. ctx.closePath();
  8742. ctx.clip();
  8743. }
  8744. function toRadiusCorners(value) {
  8745. return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
  8746. }
  8747. function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
  8748. const o = toRadiusCorners(arc.options.borderRadius);
  8749. const halfThickness = (outerRadius - innerRadius) / 2;
  8750. const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
  8751. const computeOuterLimit = (val) => {
  8752. const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
  8753. return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
  8754. };
  8755. return {
  8756. outerStart: computeOuterLimit(o.outerStart),
  8757. outerEnd: computeOuterLimit(o.outerEnd),
  8758. innerStart: _limitValue(o.innerStart, 0, innerLimit),
  8759. innerEnd: _limitValue(o.innerEnd, 0, innerLimit),
  8760. };
  8761. }
  8762. function rThetaToXY(r, theta, x, y) {
  8763. return {
  8764. x: x + r * Math.cos(theta),
  8765. y: y + r * Math.sin(theta),
  8766. };
  8767. }
  8768. function pathArc(ctx, element, offset, spacing, end) {
  8769. const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;
  8770. const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
  8771. const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
  8772. let spacingOffset = 0;
  8773. const alpha = end - start;
  8774. if (spacing) {
  8775. const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
  8776. const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
  8777. const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
  8778. const adjustedAngle = avNogSpacingRadius !== 0 ? (alpha * avNogSpacingRadius) / (avNogSpacingRadius + spacing) : alpha;
  8779. spacingOffset = (alpha - adjustedAngle) / 2;
  8780. }
  8781. const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
  8782. const angleOffset = (alpha - beta) / 2;
  8783. const startAngle = start + angleOffset + spacingOffset;
  8784. const endAngle = end - angleOffset - spacingOffset;
  8785. const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
  8786. const outerStartAdjustedRadius = outerRadius - outerStart;
  8787. const outerEndAdjustedRadius = outerRadius - outerEnd;
  8788. const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
  8789. const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
  8790. const innerStartAdjustedRadius = innerRadius + innerStart;
  8791. const innerEndAdjustedRadius = innerRadius + innerEnd;
  8792. const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
  8793. const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
  8794. ctx.beginPath();
  8795. ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
  8796. if (outerEnd > 0) {
  8797. const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
  8798. ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
  8799. }
  8800. const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
  8801. ctx.lineTo(p4.x, p4.y);
  8802. if (innerEnd > 0) {
  8803. const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
  8804. ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
  8805. }
  8806. ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);
  8807. if (innerStart > 0) {
  8808. const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
  8809. ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
  8810. }
  8811. const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
  8812. ctx.lineTo(p8.x, p8.y);
  8813. if (outerStart > 0) {
  8814. const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
  8815. ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
  8816. }
  8817. ctx.closePath();
  8818. }
  8819. function drawArc(ctx, element, offset, spacing) {
  8820. const {fullCircles, startAngle, circumference} = element;
  8821. let endAngle = element.endAngle;
  8822. if (fullCircles) {
  8823. pathArc(ctx, element, offset, spacing, startAngle + TAU);
  8824. for (let i = 0; i < fullCircles; ++i) {
  8825. ctx.fill();
  8826. }
  8827. if (!isNaN(circumference)) {
  8828. endAngle = startAngle + circumference % TAU;
  8829. if (circumference % TAU === 0) {
  8830. endAngle += TAU;
  8831. }
  8832. }
  8833. }
  8834. pathArc(ctx, element, offset, spacing, endAngle);
  8835. ctx.fill();
  8836. return endAngle;
  8837. }
  8838. function drawFullCircleBorders(ctx, element, inner) {
  8839. const {x, y, startAngle, pixelMargin, fullCircles} = element;
  8840. const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
  8841. const innerRadius = element.innerRadius + pixelMargin;
  8842. let i;
  8843. if (inner) {
  8844. clipArc(ctx, element, startAngle + TAU);
  8845. }
  8846. ctx.beginPath();
  8847. ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
  8848. for (i = 0; i < fullCircles; ++i) {
  8849. ctx.stroke();
  8850. }
  8851. ctx.beginPath();
  8852. ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
  8853. for (i = 0; i < fullCircles; ++i) {
  8854. ctx.stroke();
  8855. }
  8856. }
  8857. function drawBorder(ctx, element, offset, spacing, endAngle) {
  8858. const {options} = element;
  8859. const {borderWidth, borderJoinStyle} = options;
  8860. const inner = options.borderAlign === 'inner';
  8861. if (!borderWidth) {
  8862. return;
  8863. }
  8864. if (inner) {
  8865. ctx.lineWidth = borderWidth * 2;
  8866. ctx.lineJoin = borderJoinStyle || 'round';
  8867. } else {
  8868. ctx.lineWidth = borderWidth;
  8869. ctx.lineJoin = borderJoinStyle || 'bevel';
  8870. }
  8871. if (element.fullCircles) {
  8872. drawFullCircleBorders(ctx, element, inner);
  8873. }
  8874. if (inner) {
  8875. clipArc(ctx, element, endAngle);
  8876. }
  8877. pathArc(ctx, element, offset, spacing, endAngle);
  8878. ctx.stroke();
  8879. }
  8880. class ArcElement extends Element {
  8881. constructor(cfg) {
  8882. super();
  8883. this.options = undefined;
  8884. this.circumference = undefined;
  8885. this.startAngle = undefined;
  8886. this.endAngle = undefined;
  8887. this.innerRadius = undefined;
  8888. this.outerRadius = undefined;
  8889. this.pixelMargin = 0;
  8890. this.fullCircles = 0;
  8891. if (cfg) {
  8892. Object.assign(this, cfg);
  8893. }
  8894. }
  8895. inRange(chartX, chartY, useFinalPosition) {
  8896. const point = this.getProps(['x', 'y'], useFinalPosition);
  8897. const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});
  8898. const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
  8899. 'startAngle',
  8900. 'endAngle',
  8901. 'innerRadius',
  8902. 'outerRadius',
  8903. 'circumference'
  8904. ], useFinalPosition);
  8905. const rAdjust = this.options.spacing / 2;
  8906. const _circumference = valueOrDefault(circumference, endAngle - startAngle);
  8907. const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
  8908. const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
  8909. return (betweenAngles && withinRadius);
  8910. }
  8911. getCenterPoint(useFinalPosition) {
  8912. const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
  8913. 'x',
  8914. 'y',
  8915. 'startAngle',
  8916. 'endAngle',
  8917. 'innerRadius',
  8918. 'outerRadius',
  8919. 'circumference',
  8920. ], useFinalPosition);
  8921. const {offset, spacing} = this.options;
  8922. const halfAngle = (startAngle + endAngle) / 2;
  8923. const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
  8924. return {
  8925. x: x + Math.cos(halfAngle) * halfRadius,
  8926. y: y + Math.sin(halfAngle) * halfRadius
  8927. };
  8928. }
  8929. tooltipPosition(useFinalPosition) {
  8930. return this.getCenterPoint(useFinalPosition);
  8931. }
  8932. draw(ctx) {
  8933. const {options, circumference} = this;
  8934. const offset = (options.offset || 0) / 2;
  8935. const spacing = (options.spacing || 0) / 2;
  8936. this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
  8937. this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
  8938. if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
  8939. return;
  8940. }
  8941. ctx.save();
  8942. let radiusOffset = 0;
  8943. if (offset) {
  8944. radiusOffset = offset / 2;
  8945. const halfAngle = (this.startAngle + this.endAngle) / 2;
  8946. ctx.translate(Math.cos(halfAngle) * radiusOffset, Math.sin(halfAngle) * radiusOffset);
  8947. if (this.circumference >= PI) {
  8948. radiusOffset = offset;
  8949. }
  8950. }
  8951. ctx.fillStyle = options.backgroundColor;
  8952. ctx.strokeStyle = options.borderColor;
  8953. const endAngle = drawArc(ctx, this, radiusOffset, spacing);
  8954. drawBorder(ctx, this, radiusOffset, spacing, endAngle);
  8955. ctx.restore();
  8956. }
  8957. }
  8958. ArcElement.id = 'arc';
  8959. ArcElement.defaults = {
  8960. borderAlign: 'center',
  8961. borderColor: '#fff',
  8962. borderJoinStyle: undefined,
  8963. borderRadius: 0,
  8964. borderWidth: 2,
  8965. offset: 0,
  8966. spacing: 0,
  8967. angle: undefined,
  8968. };
  8969. ArcElement.defaultRoutes = {
  8970. backgroundColor: 'backgroundColor'
  8971. };
  8972. function setStyle(ctx, options, style = options) {
  8973. ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
  8974. ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
  8975. ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
  8976. ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
  8977. ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
  8978. ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
  8979. }
  8980. function lineTo(ctx, previous, target) {
  8981. ctx.lineTo(target.x, target.y);
  8982. }
  8983. function getLineMethod(options) {
  8984. if (options.stepped) {
  8985. return _steppedLineTo;
  8986. }
  8987. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  8988. return _bezierCurveTo;
  8989. }
  8990. return lineTo;
  8991. }
  8992. function pathVars(points, segment, params = {}) {
  8993. const count = points.length;
  8994. const {start: paramsStart = 0, end: paramsEnd = count - 1} = params;
  8995. const {start: segmentStart, end: segmentEnd} = segment;
  8996. const start = Math.max(paramsStart, segmentStart);
  8997. const end = Math.min(paramsEnd, segmentEnd);
  8998. const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
  8999. return {
  9000. count,
  9001. start,
  9002. loop: segment.loop,
  9003. ilen: end < start && !outside ? count + end - start : end - start
  9004. };
  9005. }
  9006. function pathSegment(ctx, line, segment, params) {
  9007. const {points, options} = line;
  9008. const {count, start, loop, ilen} = pathVars(points, segment, params);
  9009. const lineMethod = getLineMethod(options);
  9010. let {move = true, reverse} = params || {};
  9011. let i, point, prev;
  9012. for (i = 0; i <= ilen; ++i) {
  9013. point = points[(start + (reverse ? ilen - i : i)) % count];
  9014. if (point.skip) {
  9015. continue;
  9016. } else if (move) {
  9017. ctx.moveTo(point.x, point.y);
  9018. move = false;
  9019. } else {
  9020. lineMethod(ctx, prev, point, reverse, options.stepped);
  9021. }
  9022. prev = point;
  9023. }
  9024. if (loop) {
  9025. point = points[(start + (reverse ? ilen : 0)) % count];
  9026. lineMethod(ctx, prev, point, reverse, options.stepped);
  9027. }
  9028. return !!loop;
  9029. }
  9030. function fastPathSegment(ctx, line, segment, params) {
  9031. const points = line.points;
  9032. const {count, start, ilen} = pathVars(points, segment, params);
  9033. const {move = true, reverse} = params || {};
  9034. let avgX = 0;
  9035. let countX = 0;
  9036. let i, point, prevX, minY, maxY, lastY;
  9037. const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count;
  9038. const drawX = () => {
  9039. if (minY !== maxY) {
  9040. ctx.lineTo(avgX, maxY);
  9041. ctx.lineTo(avgX, minY);
  9042. ctx.lineTo(avgX, lastY);
  9043. }
  9044. };
  9045. if (move) {
  9046. point = points[pointIndex(0)];
  9047. ctx.moveTo(point.x, point.y);
  9048. }
  9049. for (i = 0; i <= ilen; ++i) {
  9050. point = points[pointIndex(i)];
  9051. if (point.skip) {
  9052. continue;
  9053. }
  9054. const x = point.x;
  9055. const y = point.y;
  9056. const truncX = x | 0;
  9057. if (truncX === prevX) {
  9058. if (y < minY) {
  9059. minY = y;
  9060. } else if (y > maxY) {
  9061. maxY = y;
  9062. }
  9063. avgX = (countX * avgX + x) / ++countX;
  9064. } else {
  9065. drawX();
  9066. ctx.lineTo(x, y);
  9067. prevX = truncX;
  9068. countX = 0;
  9069. minY = maxY = y;
  9070. }
  9071. lastY = y;
  9072. }
  9073. drawX();
  9074. }
  9075. function _getSegmentMethod(line) {
  9076. const opts = line.options;
  9077. const borderDash = opts.borderDash && opts.borderDash.length;
  9078. const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
  9079. return useFastPath ? fastPathSegment : pathSegment;
  9080. }
  9081. function _getInterpolationMethod(options) {
  9082. if (options.stepped) {
  9083. return _steppedInterpolation;
  9084. }
  9085. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  9086. return _bezierInterpolation;
  9087. }
  9088. return _pointInLine;
  9089. }
  9090. function strokePathWithCache(ctx, line, start, count) {
  9091. let path = line._path;
  9092. if (!path) {
  9093. path = line._path = new Path2D();
  9094. if (line.path(path, start, count)) {
  9095. path.closePath();
  9096. }
  9097. }
  9098. setStyle(ctx, line.options);
  9099. ctx.stroke(path);
  9100. }
  9101. function strokePathDirect(ctx, line, start, count) {
  9102. const {segments, options} = line;
  9103. const segmentMethod = _getSegmentMethod(line);
  9104. for (const segment of segments) {
  9105. setStyle(ctx, options, segment.style);
  9106. ctx.beginPath();
  9107. if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) {
  9108. ctx.closePath();
  9109. }
  9110. ctx.stroke();
  9111. }
  9112. }
  9113. const usePath2D = typeof Path2D === 'function';
  9114. function draw(ctx, line, start, count) {
  9115. if (usePath2D && !line.options.segment) {
  9116. strokePathWithCache(ctx, line, start, count);
  9117. } else {
  9118. strokePathDirect(ctx, line, start, count);
  9119. }
  9120. }
  9121. class LineElement extends Element {
  9122. constructor(cfg) {
  9123. super();
  9124. this.animated = true;
  9125. this.options = undefined;
  9126. this._chart = undefined;
  9127. this._loop = undefined;
  9128. this._fullLoop = undefined;
  9129. this._path = undefined;
  9130. this._points = undefined;
  9131. this._segments = undefined;
  9132. this._decimated = false;
  9133. this._pointsUpdated = false;
  9134. this._datasetIndex = undefined;
  9135. if (cfg) {
  9136. Object.assign(this, cfg);
  9137. }
  9138. }
  9139. updateControlPoints(chartArea, indexAxis) {
  9140. const options = this.options;
  9141. if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
  9142. const loop = options.spanGaps ? this._loop : this._fullLoop;
  9143. _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
  9144. this._pointsUpdated = true;
  9145. }
  9146. }
  9147. set points(points) {
  9148. this._points = points;
  9149. delete this._segments;
  9150. delete this._path;
  9151. this._pointsUpdated = false;
  9152. }
  9153. get points() {
  9154. return this._points;
  9155. }
  9156. get segments() {
  9157. return this._segments || (this._segments = _computeSegments(this, this.options.segment));
  9158. }
  9159. first() {
  9160. const segments = this.segments;
  9161. const points = this.points;
  9162. return segments.length && points[segments[0].start];
  9163. }
  9164. last() {
  9165. const segments = this.segments;
  9166. const points = this.points;
  9167. const count = segments.length;
  9168. return count && points[segments[count - 1].end];
  9169. }
  9170. interpolate(point, property) {
  9171. const options = this.options;
  9172. const value = point[property];
  9173. const points = this.points;
  9174. const segments = _boundSegments(this, {property, start: value, end: value});
  9175. if (!segments.length) {
  9176. return;
  9177. }
  9178. const result = [];
  9179. const _interpolate = _getInterpolationMethod(options);
  9180. let i, ilen;
  9181. for (i = 0, ilen = segments.length; i < ilen; ++i) {
  9182. const {start, end} = segments[i];
  9183. const p1 = points[start];
  9184. const p2 = points[end];
  9185. if (p1 === p2) {
  9186. result.push(p1);
  9187. continue;
  9188. }
  9189. const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
  9190. const interpolated = _interpolate(p1, p2, t, options.stepped);
  9191. interpolated[property] = point[property];
  9192. result.push(interpolated);
  9193. }
  9194. return result.length === 1 ? result[0] : result;
  9195. }
  9196. pathSegment(ctx, segment, params) {
  9197. const segmentMethod = _getSegmentMethod(this);
  9198. return segmentMethod(ctx, this, segment, params);
  9199. }
  9200. path(ctx, start, count) {
  9201. const segments = this.segments;
  9202. const segmentMethod = _getSegmentMethod(this);
  9203. let loop = this._loop;
  9204. start = start || 0;
  9205. count = count || (this.points.length - start);
  9206. for (const segment of segments) {
  9207. loop &= segmentMethod(ctx, this, segment, {start, end: start + count - 1});
  9208. }
  9209. return !!loop;
  9210. }
  9211. draw(ctx, chartArea, start, count) {
  9212. const options = this.options || {};
  9213. const points = this.points || [];
  9214. if (points.length && options.borderWidth) {
  9215. ctx.save();
  9216. draw(ctx, this, start, count);
  9217. ctx.restore();
  9218. }
  9219. if (this.animated) {
  9220. this._pointsUpdated = false;
  9221. this._path = undefined;
  9222. }
  9223. }
  9224. }
  9225. LineElement.id = 'line';
  9226. LineElement.defaults = {
  9227. borderCapStyle: 'butt',
  9228. borderDash: [],
  9229. borderDashOffset: 0,
  9230. borderJoinStyle: 'miter',
  9231. borderWidth: 3,
  9232. capBezierPoints: true,
  9233. cubicInterpolationMode: 'default',
  9234. fill: false,
  9235. spanGaps: false,
  9236. stepped: false,
  9237. tension: 0,
  9238. };
  9239. LineElement.defaultRoutes = {
  9240. backgroundColor: 'backgroundColor',
  9241. borderColor: 'borderColor'
  9242. };
  9243. LineElement.descriptors = {
  9244. _scriptable: true,
  9245. _indexable: (name) => name !== 'borderDash' && name !== 'fill',
  9246. };
  9247. function inRange$1(el, pos, axis, useFinalPosition) {
  9248. const options = el.options;
  9249. const {[axis]: value} = el.getProps([axis], useFinalPosition);
  9250. return (Math.abs(pos - value) < options.radius + options.hitRadius);
  9251. }
  9252. class PointElement extends Element {
  9253. constructor(cfg) {
  9254. super();
  9255. this.options = undefined;
  9256. this.parsed = undefined;
  9257. this.skip = undefined;
  9258. this.stop = undefined;
  9259. if (cfg) {
  9260. Object.assign(this, cfg);
  9261. }
  9262. }
  9263. inRange(mouseX, mouseY, useFinalPosition) {
  9264. const options = this.options;
  9265. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  9266. return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
  9267. }
  9268. inXRange(mouseX, useFinalPosition) {
  9269. return inRange$1(this, mouseX, 'x', useFinalPosition);
  9270. }
  9271. inYRange(mouseY, useFinalPosition) {
  9272. return inRange$1(this, mouseY, 'y', useFinalPosition);
  9273. }
  9274. getCenterPoint(useFinalPosition) {
  9275. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  9276. return {x, y};
  9277. }
  9278. size(options) {
  9279. options = options || this.options || {};
  9280. let radius = options.radius || 0;
  9281. radius = Math.max(radius, radius && options.hoverRadius || 0);
  9282. const borderWidth = radius && options.borderWidth || 0;
  9283. return (radius + borderWidth) * 2;
  9284. }
  9285. draw(ctx, area) {
  9286. const options = this.options;
  9287. if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
  9288. return;
  9289. }
  9290. ctx.strokeStyle = options.borderColor;
  9291. ctx.lineWidth = options.borderWidth;
  9292. ctx.fillStyle = options.backgroundColor;
  9293. drawPoint(ctx, options, this.x, this.y);
  9294. }
  9295. getRange() {
  9296. const options = this.options || {};
  9297. return options.radius + options.hitRadius;
  9298. }
  9299. }
  9300. PointElement.id = 'point';
  9301. PointElement.defaults = {
  9302. borderWidth: 1,
  9303. hitRadius: 1,
  9304. hoverBorderWidth: 1,
  9305. hoverRadius: 4,
  9306. pointStyle: 'circle',
  9307. radius: 3,
  9308. rotation: 0
  9309. };
  9310. PointElement.defaultRoutes = {
  9311. backgroundColor: 'backgroundColor',
  9312. borderColor: 'borderColor'
  9313. };
  9314. function getBarBounds(bar, useFinalPosition) {
  9315. const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
  9316. let left, right, top, bottom, half;
  9317. if (bar.horizontal) {
  9318. half = height / 2;
  9319. left = Math.min(x, base);
  9320. right = Math.max(x, base);
  9321. top = y - half;
  9322. bottom = y + half;
  9323. } else {
  9324. half = width / 2;
  9325. left = x - half;
  9326. right = x + half;
  9327. top = Math.min(y, base);
  9328. bottom = Math.max(y, base);
  9329. }
  9330. return {left, top, right, bottom};
  9331. }
  9332. function skipOrLimit(skip, value, min, max) {
  9333. return skip ? 0 : _limitValue(value, min, max);
  9334. }
  9335. function parseBorderWidth(bar, maxW, maxH) {
  9336. const value = bar.options.borderWidth;
  9337. const skip = bar.borderSkipped;
  9338. const o = toTRBL(value);
  9339. return {
  9340. t: skipOrLimit(skip.top, o.top, 0, maxH),
  9341. r: skipOrLimit(skip.right, o.right, 0, maxW),
  9342. b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
  9343. l: skipOrLimit(skip.left, o.left, 0, maxW)
  9344. };
  9345. }
  9346. function parseBorderRadius(bar, maxW, maxH) {
  9347. const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);
  9348. const value = bar.options.borderRadius;
  9349. const o = toTRBLCorners(value);
  9350. const maxR = Math.min(maxW, maxH);
  9351. const skip = bar.borderSkipped;
  9352. const enableBorder = enableBorderRadius || isObject(value);
  9353. return {
  9354. topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
  9355. topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
  9356. bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
  9357. bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
  9358. };
  9359. }
  9360. function boundingRects(bar) {
  9361. const bounds = getBarBounds(bar);
  9362. const width = bounds.right - bounds.left;
  9363. const height = bounds.bottom - bounds.top;
  9364. const border = parseBorderWidth(bar, width / 2, height / 2);
  9365. const radius = parseBorderRadius(bar, width / 2, height / 2);
  9366. return {
  9367. outer: {
  9368. x: bounds.left,
  9369. y: bounds.top,
  9370. w: width,
  9371. h: height,
  9372. radius
  9373. },
  9374. inner: {
  9375. x: bounds.left + border.l,
  9376. y: bounds.top + border.t,
  9377. w: width - border.l - border.r,
  9378. h: height - border.t - border.b,
  9379. radius: {
  9380. topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
  9381. topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
  9382. bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
  9383. bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
  9384. }
  9385. }
  9386. };
  9387. }
  9388. function inRange(bar, x, y, useFinalPosition) {
  9389. const skipX = x === null;
  9390. const skipY = y === null;
  9391. const skipBoth = skipX && skipY;
  9392. const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
  9393. return bounds
  9394. && (skipX || _isBetween(x, bounds.left, bounds.right))
  9395. && (skipY || _isBetween(y, bounds.top, bounds.bottom));
  9396. }
  9397. function hasRadius(radius) {
  9398. return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  9399. }
  9400. function addNormalRectPath(ctx, rect) {
  9401. ctx.rect(rect.x, rect.y, rect.w, rect.h);
  9402. }
  9403. function inflateRect(rect, amount, refRect = {}) {
  9404. const x = rect.x !== refRect.x ? -amount : 0;
  9405. const y = rect.y !== refRect.y ? -amount : 0;
  9406. const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
  9407. const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
  9408. return {
  9409. x: rect.x + x,
  9410. y: rect.y + y,
  9411. w: rect.w + w,
  9412. h: rect.h + h,
  9413. radius: rect.radius
  9414. };
  9415. }
  9416. class BarElement extends Element {
  9417. constructor(cfg) {
  9418. super();
  9419. this.options = undefined;
  9420. this.horizontal = undefined;
  9421. this.base = undefined;
  9422. this.width = undefined;
  9423. this.height = undefined;
  9424. this.inflateAmount = undefined;
  9425. if (cfg) {
  9426. Object.assign(this, cfg);
  9427. }
  9428. }
  9429. draw(ctx) {
  9430. const {inflateAmount, options: {borderColor, backgroundColor}} = this;
  9431. const {inner, outer} = boundingRects(this);
  9432. const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
  9433. ctx.save();
  9434. if (outer.w !== inner.w || outer.h !== inner.h) {
  9435. ctx.beginPath();
  9436. addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
  9437. ctx.clip();
  9438. addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
  9439. ctx.fillStyle = borderColor;
  9440. ctx.fill('evenodd');
  9441. }
  9442. ctx.beginPath();
  9443. addRectPath(ctx, inflateRect(inner, inflateAmount));
  9444. ctx.fillStyle = backgroundColor;
  9445. ctx.fill();
  9446. ctx.restore();
  9447. }
  9448. inRange(mouseX, mouseY, useFinalPosition) {
  9449. return inRange(this, mouseX, mouseY, useFinalPosition);
  9450. }
  9451. inXRange(mouseX, useFinalPosition) {
  9452. return inRange(this, mouseX, null, useFinalPosition);
  9453. }
  9454. inYRange(mouseY, useFinalPosition) {
  9455. return inRange(this, null, mouseY, useFinalPosition);
  9456. }
  9457. getCenterPoint(useFinalPosition) {
  9458. const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition);
  9459. return {
  9460. x: horizontal ? (x + base) / 2 : x,
  9461. y: horizontal ? y : (y + base) / 2
  9462. };
  9463. }
  9464. getRange(axis) {
  9465. return axis === 'x' ? this.width / 2 : this.height / 2;
  9466. }
  9467. }
  9468. BarElement.id = 'bar';
  9469. BarElement.defaults = {
  9470. borderSkipped: 'start',
  9471. borderWidth: 0,
  9472. borderRadius: 0,
  9473. inflateAmount: 'auto',
  9474. pointStyle: undefined
  9475. };
  9476. BarElement.defaultRoutes = {
  9477. backgroundColor: 'backgroundColor',
  9478. borderColor: 'borderColor'
  9479. };
  9480. var elements = /*#__PURE__*/Object.freeze({
  9481. __proto__: null,
  9482. ArcElement: ArcElement,
  9483. LineElement: LineElement,
  9484. PointElement: PointElement,
  9485. BarElement: BarElement
  9486. });
  9487. function lttbDecimation(data, start, count, availableWidth, options) {
  9488. const samples = options.samples || availableWidth;
  9489. if (samples >= count) {
  9490. return data.slice(start, start + count);
  9491. }
  9492. const decimated = [];
  9493. const bucketWidth = (count - 2) / (samples - 2);
  9494. let sampledIndex = 0;
  9495. const endIndex = start + count - 1;
  9496. let a = start;
  9497. let i, maxAreaPoint, maxArea, area, nextA;
  9498. decimated[sampledIndex++] = data[a];
  9499. for (i = 0; i < samples - 2; i++) {
  9500. let avgX = 0;
  9501. let avgY = 0;
  9502. let j;
  9503. const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
  9504. const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
  9505. const avgRangeLength = avgRangeEnd - avgRangeStart;
  9506. for (j = avgRangeStart; j < avgRangeEnd; j++) {
  9507. avgX += data[j].x;
  9508. avgY += data[j].y;
  9509. }
  9510. avgX /= avgRangeLength;
  9511. avgY /= avgRangeLength;
  9512. const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
  9513. const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
  9514. const {x: pointAx, y: pointAy} = data[a];
  9515. maxArea = area = -1;
  9516. for (j = rangeOffs; j < rangeTo; j++) {
  9517. area = 0.5 * Math.abs(
  9518. (pointAx - avgX) * (data[j].y - pointAy) -
  9519. (pointAx - data[j].x) * (avgY - pointAy)
  9520. );
  9521. if (area > maxArea) {
  9522. maxArea = area;
  9523. maxAreaPoint = data[j];
  9524. nextA = j;
  9525. }
  9526. }
  9527. decimated[sampledIndex++] = maxAreaPoint;
  9528. a = nextA;
  9529. }
  9530. decimated[sampledIndex++] = data[endIndex];
  9531. return decimated;
  9532. }
  9533. function minMaxDecimation(data, start, count, availableWidth) {
  9534. let avgX = 0;
  9535. let countX = 0;
  9536. let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
  9537. const decimated = [];
  9538. const endIndex = start + count - 1;
  9539. const xMin = data[start].x;
  9540. const xMax = data[endIndex].x;
  9541. const dx = xMax - xMin;
  9542. for (i = start; i < start + count; ++i) {
  9543. point = data[i];
  9544. x = (point.x - xMin) / dx * availableWidth;
  9545. y = point.y;
  9546. const truncX = x | 0;
  9547. if (truncX === prevX) {
  9548. if (y < minY) {
  9549. minY = y;
  9550. minIndex = i;
  9551. } else if (y > maxY) {
  9552. maxY = y;
  9553. maxIndex = i;
  9554. }
  9555. avgX = (countX * avgX + point.x) / ++countX;
  9556. } else {
  9557. const lastIndex = i - 1;
  9558. if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
  9559. const intermediateIndex1 = Math.min(minIndex, maxIndex);
  9560. const intermediateIndex2 = Math.max(minIndex, maxIndex);
  9561. if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
  9562. decimated.push({
  9563. ...data[intermediateIndex1],
  9564. x: avgX,
  9565. });
  9566. }
  9567. if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
  9568. decimated.push({
  9569. ...data[intermediateIndex2],
  9570. x: avgX
  9571. });
  9572. }
  9573. }
  9574. if (i > 0 && lastIndex !== startIndex) {
  9575. decimated.push(data[lastIndex]);
  9576. }
  9577. decimated.push(point);
  9578. prevX = truncX;
  9579. countX = 0;
  9580. minY = maxY = y;
  9581. minIndex = maxIndex = startIndex = i;
  9582. }
  9583. }
  9584. return decimated;
  9585. }
  9586. function cleanDecimatedDataset(dataset) {
  9587. if (dataset._decimated) {
  9588. const data = dataset._data;
  9589. delete dataset._decimated;
  9590. delete dataset._data;
  9591. Object.defineProperty(dataset, 'data', {value: data});
  9592. }
  9593. }
  9594. function cleanDecimatedData(chart) {
  9595. chart.data.datasets.forEach((dataset) => {
  9596. cleanDecimatedDataset(dataset);
  9597. });
  9598. }
  9599. function getStartAndCountOfVisiblePointsSimplified(meta, points) {
  9600. const pointCount = points.length;
  9601. let start = 0;
  9602. let count;
  9603. const {iScale} = meta;
  9604. const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
  9605. if (minDefined) {
  9606. start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
  9607. }
  9608. if (maxDefined) {
  9609. count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
  9610. } else {
  9611. count = pointCount - start;
  9612. }
  9613. return {start, count};
  9614. }
  9615. var plugin_decimation = {
  9616. id: 'decimation',
  9617. defaults: {
  9618. algorithm: 'min-max',
  9619. enabled: false,
  9620. },
  9621. beforeElementsUpdate: (chart, args, options) => {
  9622. if (!options.enabled) {
  9623. cleanDecimatedData(chart);
  9624. return;
  9625. }
  9626. const availableWidth = chart.width;
  9627. chart.data.datasets.forEach((dataset, datasetIndex) => {
  9628. const {_data, indexAxis} = dataset;
  9629. const meta = chart.getDatasetMeta(datasetIndex);
  9630. const data = _data || dataset.data;
  9631. if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {
  9632. return;
  9633. }
  9634. if (meta.type !== 'line') {
  9635. return;
  9636. }
  9637. const xAxis = chart.scales[meta.xAxisID];
  9638. if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
  9639. return;
  9640. }
  9641. if (chart.options.parsing) {
  9642. return;
  9643. }
  9644. let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);
  9645. const threshold = options.threshold || 4 * availableWidth;
  9646. if (count <= threshold) {
  9647. cleanDecimatedDataset(dataset);
  9648. return;
  9649. }
  9650. if (isNullOrUndef(_data)) {
  9651. dataset._data = data;
  9652. delete dataset.data;
  9653. Object.defineProperty(dataset, 'data', {
  9654. configurable: true,
  9655. enumerable: true,
  9656. get: function() {
  9657. return this._decimated;
  9658. },
  9659. set: function(d) {
  9660. this._data = d;
  9661. }
  9662. });
  9663. }
  9664. let decimated;
  9665. switch (options.algorithm) {
  9666. case 'lttb':
  9667. decimated = lttbDecimation(data, start, count, availableWidth, options);
  9668. break;
  9669. case 'min-max':
  9670. decimated = minMaxDecimation(data, start, count, availableWidth);
  9671. break;
  9672. default:
  9673. throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
  9674. }
  9675. dataset._decimated = decimated;
  9676. });
  9677. },
  9678. destroy(chart) {
  9679. cleanDecimatedData(chart);
  9680. }
  9681. };
  9682. function getLineByIndex(chart, index) {
  9683. const meta = chart.getDatasetMeta(index);
  9684. const visible = meta && chart.isDatasetVisible(index);
  9685. return visible ? meta.dataset : null;
  9686. }
  9687. function parseFillOption(line) {
  9688. const options = line.options;
  9689. const fillOption = options.fill;
  9690. let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
  9691. if (fill === undefined) {
  9692. fill = !!options.backgroundColor;
  9693. }
  9694. if (fill === false || fill === null) {
  9695. return false;
  9696. }
  9697. if (fill === true) {
  9698. return 'origin';
  9699. }
  9700. return fill;
  9701. }
  9702. function decodeFill(line, index, count) {
  9703. const fill = parseFillOption(line);
  9704. if (isObject(fill)) {
  9705. return isNaN(fill.value) ? false : fill;
  9706. }
  9707. let target = parseFloat(fill);
  9708. if (isNumberFinite(target) && Math.floor(target) === target) {
  9709. if (fill[0] === '-' || fill[0] === '+') {
  9710. target = index + target;
  9711. }
  9712. if (target === index || target < 0 || target >= count) {
  9713. return false;
  9714. }
  9715. return target;
  9716. }
  9717. return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;
  9718. }
  9719. function computeLinearBoundary(source) {
  9720. const {scale = {}, fill} = source;
  9721. let target = null;
  9722. let horizontal;
  9723. if (fill === 'start') {
  9724. target = scale.bottom;
  9725. } else if (fill === 'end') {
  9726. target = scale.top;
  9727. } else if (isObject(fill)) {
  9728. target = scale.getPixelForValue(fill.value);
  9729. } else if (scale.getBasePixel) {
  9730. target = scale.getBasePixel();
  9731. }
  9732. if (isNumberFinite(target)) {
  9733. horizontal = scale.isHorizontal();
  9734. return {
  9735. x: horizontal ? target : null,
  9736. y: horizontal ? null : target
  9737. };
  9738. }
  9739. return null;
  9740. }
  9741. class simpleArc {
  9742. constructor(opts) {
  9743. this.x = opts.x;
  9744. this.y = opts.y;
  9745. this.radius = opts.radius;
  9746. }
  9747. pathSegment(ctx, bounds, opts) {
  9748. const {x, y, radius} = this;
  9749. bounds = bounds || {start: 0, end: TAU};
  9750. ctx.arc(x, y, radius, bounds.end, bounds.start, true);
  9751. return !opts.bounds;
  9752. }
  9753. interpolate(point) {
  9754. const {x, y, radius} = this;
  9755. const angle = point.angle;
  9756. return {
  9757. x: x + Math.cos(angle) * radius,
  9758. y: y + Math.sin(angle) * radius,
  9759. angle
  9760. };
  9761. }
  9762. }
  9763. function computeCircularBoundary(source) {
  9764. const {scale, fill} = source;
  9765. const options = scale.options;
  9766. const length = scale.getLabels().length;
  9767. const target = [];
  9768. const start = options.reverse ? scale.max : scale.min;
  9769. const end = options.reverse ? scale.min : scale.max;
  9770. let i, center, value;
  9771. if (fill === 'start') {
  9772. value = start;
  9773. } else if (fill === 'end') {
  9774. value = end;
  9775. } else if (isObject(fill)) {
  9776. value = fill.value;
  9777. } else {
  9778. value = scale.getBaseValue();
  9779. }
  9780. if (options.grid.circular) {
  9781. center = scale.getPointPositionForValue(0, start);
  9782. return new simpleArc({
  9783. x: center.x,
  9784. y: center.y,
  9785. radius: scale.getDistanceFromCenterForValue(value)
  9786. });
  9787. }
  9788. for (i = 0; i < length; ++i) {
  9789. target.push(scale.getPointPositionForValue(i, value));
  9790. }
  9791. return target;
  9792. }
  9793. function computeBoundary(source) {
  9794. const scale = source.scale || {};
  9795. if (scale.getPointPositionForValue) {
  9796. return computeCircularBoundary(source);
  9797. }
  9798. return computeLinearBoundary(source);
  9799. }
  9800. function findSegmentEnd(start, end, points) {
  9801. for (;end > start; end--) {
  9802. const point = points[end];
  9803. if (!isNaN(point.x) && !isNaN(point.y)) {
  9804. break;
  9805. }
  9806. }
  9807. return end;
  9808. }
  9809. function pointsFromSegments(boundary, line) {
  9810. const {x = null, y = null} = boundary || {};
  9811. const linePoints = line.points;
  9812. const points = [];
  9813. line.segments.forEach(({start, end}) => {
  9814. end = findSegmentEnd(start, end, linePoints);
  9815. const first = linePoints[start];
  9816. const last = linePoints[end];
  9817. if (y !== null) {
  9818. points.push({x: first.x, y});
  9819. points.push({x: last.x, y});
  9820. } else if (x !== null) {
  9821. points.push({x, y: first.y});
  9822. points.push({x, y: last.y});
  9823. }
  9824. });
  9825. return points;
  9826. }
  9827. function buildStackLine(source) {
  9828. const {scale, index, line} = source;
  9829. const points = [];
  9830. const segments = line.segments;
  9831. const sourcePoints = line.points;
  9832. const linesBelow = getLinesBelow(scale, index);
  9833. linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
  9834. for (let i = 0; i < segments.length; i++) {
  9835. const segment = segments[i];
  9836. for (let j = segment.start; j <= segment.end; j++) {
  9837. addPointsBelow(points, sourcePoints[j], linesBelow);
  9838. }
  9839. }
  9840. return new LineElement({points, options: {}});
  9841. }
  9842. function getLinesBelow(scale, index) {
  9843. const below = [];
  9844. const metas = scale.getMatchingVisibleMetas('line');
  9845. for (let i = 0; i < metas.length; i++) {
  9846. const meta = metas[i];
  9847. if (meta.index === index) {
  9848. break;
  9849. }
  9850. if (!meta.hidden) {
  9851. below.unshift(meta.dataset);
  9852. }
  9853. }
  9854. return below;
  9855. }
  9856. function addPointsBelow(points, sourcePoint, linesBelow) {
  9857. const postponed = [];
  9858. for (let j = 0; j < linesBelow.length; j++) {
  9859. const line = linesBelow[j];
  9860. const {first, last, point} = findPoint(line, sourcePoint, 'x');
  9861. if (!point || (first && last)) {
  9862. continue;
  9863. }
  9864. if (first) {
  9865. postponed.unshift(point);
  9866. } else {
  9867. points.push(point);
  9868. if (!last) {
  9869. break;
  9870. }
  9871. }
  9872. }
  9873. points.push(...postponed);
  9874. }
  9875. function findPoint(line, sourcePoint, property) {
  9876. const point = line.interpolate(sourcePoint, property);
  9877. if (!point) {
  9878. return {};
  9879. }
  9880. const pointValue = point[property];
  9881. const segments = line.segments;
  9882. const linePoints = line.points;
  9883. let first = false;
  9884. let last = false;
  9885. for (let i = 0; i < segments.length; i++) {
  9886. const segment = segments[i];
  9887. const firstValue = linePoints[segment.start][property];
  9888. const lastValue = linePoints[segment.end][property];
  9889. if (_isBetween(pointValue, firstValue, lastValue)) {
  9890. first = pointValue === firstValue;
  9891. last = pointValue === lastValue;
  9892. break;
  9893. }
  9894. }
  9895. return {first, last, point};
  9896. }
  9897. function getTarget(source) {
  9898. const {chart, fill, line} = source;
  9899. if (isNumberFinite(fill)) {
  9900. return getLineByIndex(chart, fill);
  9901. }
  9902. if (fill === 'stack') {
  9903. return buildStackLine(source);
  9904. }
  9905. if (fill === 'shape') {
  9906. return true;
  9907. }
  9908. const boundary = computeBoundary(source);
  9909. if (boundary instanceof simpleArc) {
  9910. return boundary;
  9911. }
  9912. return createBoundaryLine(boundary, line);
  9913. }
  9914. function createBoundaryLine(boundary, line) {
  9915. let points = [];
  9916. let _loop = false;
  9917. if (isArray(boundary)) {
  9918. _loop = true;
  9919. points = boundary;
  9920. } else {
  9921. points = pointsFromSegments(boundary, line);
  9922. }
  9923. return points.length ? new LineElement({
  9924. points,
  9925. options: {tension: 0},
  9926. _loop,
  9927. _fullLoop: _loop
  9928. }) : null;
  9929. }
  9930. function resolveTarget(sources, index, propagate) {
  9931. const source = sources[index];
  9932. let fill = source.fill;
  9933. const visited = [index];
  9934. let target;
  9935. if (!propagate) {
  9936. return fill;
  9937. }
  9938. while (fill !== false && visited.indexOf(fill) === -1) {
  9939. if (!isNumberFinite(fill)) {
  9940. return fill;
  9941. }
  9942. target = sources[fill];
  9943. if (!target) {
  9944. return false;
  9945. }
  9946. if (target.visible) {
  9947. return fill;
  9948. }
  9949. visited.push(fill);
  9950. fill = target.fill;
  9951. }
  9952. return false;
  9953. }
  9954. function _clip(ctx, target, clipY) {
  9955. const {segments, points} = target;
  9956. let first = true;
  9957. let lineLoop = false;
  9958. ctx.beginPath();
  9959. for (const segment of segments) {
  9960. const {start, end} = segment;
  9961. const firstPoint = points[start];
  9962. const lastPoint = points[findSegmentEnd(start, end, points)];
  9963. if (first) {
  9964. ctx.moveTo(firstPoint.x, firstPoint.y);
  9965. first = false;
  9966. } else {
  9967. ctx.lineTo(firstPoint.x, clipY);
  9968. ctx.lineTo(firstPoint.x, firstPoint.y);
  9969. }
  9970. lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});
  9971. if (lineLoop) {
  9972. ctx.closePath();
  9973. } else {
  9974. ctx.lineTo(lastPoint.x, clipY);
  9975. }
  9976. }
  9977. ctx.lineTo(target.first().x, clipY);
  9978. ctx.closePath();
  9979. ctx.clip();
  9980. }
  9981. function getBounds(property, first, last, loop) {
  9982. if (loop) {
  9983. return;
  9984. }
  9985. let start = first[property];
  9986. let end = last[property];
  9987. if (property === 'angle') {
  9988. start = _normalizeAngle(start);
  9989. end = _normalizeAngle(end);
  9990. }
  9991. return {property, start, end};
  9992. }
  9993. function _getEdge(a, b, prop, fn) {
  9994. if (a && b) {
  9995. return fn(a[prop], b[prop]);
  9996. }
  9997. return a ? a[prop] : b ? b[prop] : 0;
  9998. }
  9999. function _segments(line, target, property) {
  10000. const segments = line.segments;
  10001. const points = line.points;
  10002. const tpoints = target.points;
  10003. const parts = [];
  10004. for (const segment of segments) {
  10005. let {start, end} = segment;
  10006. end = findSegmentEnd(start, end, points);
  10007. const bounds = getBounds(property, points[start], points[end], segment.loop);
  10008. if (!target.segments) {
  10009. parts.push({
  10010. source: segment,
  10011. target: bounds,
  10012. start: points[start],
  10013. end: points[end]
  10014. });
  10015. continue;
  10016. }
  10017. const targetSegments = _boundSegments(target, bounds);
  10018. for (const tgt of targetSegments) {
  10019. const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
  10020. const fillSources = _boundSegment(segment, points, subBounds);
  10021. for (const fillSource of fillSources) {
  10022. parts.push({
  10023. source: fillSource,
  10024. target: tgt,
  10025. start: {
  10026. [property]: _getEdge(bounds, subBounds, 'start', Math.max)
  10027. },
  10028. end: {
  10029. [property]: _getEdge(bounds, subBounds, 'end', Math.min)
  10030. }
  10031. });
  10032. }
  10033. }
  10034. }
  10035. return parts;
  10036. }
  10037. function clipBounds(ctx, scale, bounds) {
  10038. const {top, bottom} = scale.chart.chartArea;
  10039. const {property, start, end} = bounds || {};
  10040. if (property === 'x') {
  10041. ctx.beginPath();
  10042. ctx.rect(start, top, end - start, bottom - top);
  10043. ctx.clip();
  10044. }
  10045. }
  10046. function interpolatedLineTo(ctx, target, point, property) {
  10047. const interpolatedPoint = target.interpolate(point, property);
  10048. if (interpolatedPoint) {
  10049. ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  10050. }
  10051. }
  10052. function _fill(ctx, cfg) {
  10053. const {line, target, property, color, scale} = cfg;
  10054. const segments = _segments(line, target, property);
  10055. for (const {source: src, target: tgt, start, end} of segments) {
  10056. const {style: {backgroundColor = color} = {}} = src;
  10057. const notShape = target !== true;
  10058. ctx.save();
  10059. ctx.fillStyle = backgroundColor;
  10060. clipBounds(ctx, scale, notShape && getBounds(property, start, end));
  10061. ctx.beginPath();
  10062. const lineLoop = !!line.pathSegment(ctx, src);
  10063. let loop;
  10064. if (notShape) {
  10065. if (lineLoop) {
  10066. ctx.closePath();
  10067. } else {
  10068. interpolatedLineTo(ctx, target, end, property);
  10069. }
  10070. const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});
  10071. loop = lineLoop && targetLoop;
  10072. if (!loop) {
  10073. interpolatedLineTo(ctx, target, start, property);
  10074. }
  10075. }
  10076. ctx.closePath();
  10077. ctx.fill(loop ? 'evenodd' : 'nonzero');
  10078. ctx.restore();
  10079. }
  10080. }
  10081. function doFill(ctx, cfg) {
  10082. const {line, target, above, below, area, scale} = cfg;
  10083. const property = line._loop ? 'angle' : cfg.axis;
  10084. ctx.save();
  10085. if (property === 'x' && below !== above) {
  10086. _clip(ctx, target, area.top);
  10087. _fill(ctx, {line, target, color: above, scale, property});
  10088. ctx.restore();
  10089. ctx.save();
  10090. _clip(ctx, target, area.bottom);
  10091. }
  10092. _fill(ctx, {line, target, color: below, scale, property});
  10093. ctx.restore();
  10094. }
  10095. function drawfill(ctx, source, area) {
  10096. const target = getTarget(source);
  10097. const {line, scale, axis} = source;
  10098. const lineOpts = line.options;
  10099. const fillOption = lineOpts.fill;
  10100. const color = lineOpts.backgroundColor;
  10101. const {above = color, below = color} = fillOption || {};
  10102. if (target && line.points.length) {
  10103. clipArea(ctx, area);
  10104. doFill(ctx, {line, target, above, below, area, scale, axis});
  10105. unclipArea(ctx);
  10106. }
  10107. }
  10108. var plugin_filler = {
  10109. id: 'filler',
  10110. afterDatasetsUpdate(chart, _args, options) {
  10111. const count = (chart.data.datasets || []).length;
  10112. const sources = [];
  10113. let meta, i, line, source;
  10114. for (i = 0; i < count; ++i) {
  10115. meta = chart.getDatasetMeta(i);
  10116. line = meta.dataset;
  10117. source = null;
  10118. if (line && line.options && line instanceof LineElement) {
  10119. source = {
  10120. visible: chart.isDatasetVisible(i),
  10121. index: i,
  10122. fill: decodeFill(line, i, count),
  10123. chart,
  10124. axis: meta.controller.options.indexAxis,
  10125. scale: meta.vScale,
  10126. line,
  10127. };
  10128. }
  10129. meta.$filler = source;
  10130. sources.push(source);
  10131. }
  10132. for (i = 0; i < count; ++i) {
  10133. source = sources[i];
  10134. if (!source || source.fill === false) {
  10135. continue;
  10136. }
  10137. source.fill = resolveTarget(sources, i, options.propagate);
  10138. }
  10139. },
  10140. beforeDraw(chart, _args, options) {
  10141. const draw = options.drawTime === 'beforeDraw';
  10142. const metasets = chart.getSortedVisibleDatasetMetas();
  10143. const area = chart.chartArea;
  10144. for (let i = metasets.length - 1; i >= 0; --i) {
  10145. const source = metasets[i].$filler;
  10146. if (!source) {
  10147. continue;
  10148. }
  10149. source.line.updateControlPoints(area, source.axis);
  10150. if (draw) {
  10151. drawfill(chart.ctx, source, area);
  10152. }
  10153. }
  10154. },
  10155. beforeDatasetsDraw(chart, _args, options) {
  10156. if (options.drawTime !== 'beforeDatasetsDraw') {
  10157. return;
  10158. }
  10159. const metasets = chart.getSortedVisibleDatasetMetas();
  10160. for (let i = metasets.length - 1; i >= 0; --i) {
  10161. const source = metasets[i].$filler;
  10162. if (source) {
  10163. drawfill(chart.ctx, source, chart.chartArea);
  10164. }
  10165. }
  10166. },
  10167. beforeDatasetDraw(chart, args, options) {
  10168. const source = args.meta.$filler;
  10169. if (!source || source.fill === false || options.drawTime !== 'beforeDatasetDraw') {
  10170. return;
  10171. }
  10172. drawfill(chart.ctx, source, chart.chartArea);
  10173. },
  10174. defaults: {
  10175. propagate: true,
  10176. drawTime: 'beforeDatasetDraw'
  10177. }
  10178. };
  10179. const getBoxSize = (labelOpts, fontSize) => {
  10180. let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts;
  10181. if (labelOpts.usePointStyle) {
  10182. boxHeight = Math.min(boxHeight, fontSize);
  10183. boxWidth = Math.min(boxWidth, fontSize);
  10184. }
  10185. return {
  10186. boxWidth,
  10187. boxHeight,
  10188. itemHeight: Math.max(fontSize, boxHeight)
  10189. };
  10190. };
  10191. const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  10192. class Legend extends Element {
  10193. constructor(config) {
  10194. super();
  10195. this._added = false;
  10196. this.legendHitBoxes = [];
  10197. this._hoveredItem = null;
  10198. this.doughnutMode = false;
  10199. this.chart = config.chart;
  10200. this.options = config.options;
  10201. this.ctx = config.ctx;
  10202. this.legendItems = undefined;
  10203. this.columnSizes = undefined;
  10204. this.lineWidths = undefined;
  10205. this.maxHeight = undefined;
  10206. this.maxWidth = undefined;
  10207. this.top = undefined;
  10208. this.bottom = undefined;
  10209. this.left = undefined;
  10210. this.right = undefined;
  10211. this.height = undefined;
  10212. this.width = undefined;
  10213. this._margins = undefined;
  10214. this.position = undefined;
  10215. this.weight = undefined;
  10216. this.fullSize = undefined;
  10217. }
  10218. update(maxWidth, maxHeight, margins) {
  10219. this.maxWidth = maxWidth;
  10220. this.maxHeight = maxHeight;
  10221. this._margins = margins;
  10222. this.setDimensions();
  10223. this.buildLabels();
  10224. this.fit();
  10225. }
  10226. setDimensions() {
  10227. if (this.isHorizontal()) {
  10228. this.width = this.maxWidth;
  10229. this.left = this._margins.left;
  10230. this.right = this.width;
  10231. } else {
  10232. this.height = this.maxHeight;
  10233. this.top = this._margins.top;
  10234. this.bottom = this.height;
  10235. }
  10236. }
  10237. buildLabels() {
  10238. const labelOpts = this.options.labels || {};
  10239. let legendItems = callback(labelOpts.generateLabels, [this.chart], this) || [];
  10240. if (labelOpts.filter) {
  10241. legendItems = legendItems.filter((item) => labelOpts.filter(item, this.chart.data));
  10242. }
  10243. if (labelOpts.sort) {
  10244. legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, this.chart.data));
  10245. }
  10246. if (this.options.reverse) {
  10247. legendItems.reverse();
  10248. }
  10249. this.legendItems = legendItems;
  10250. }
  10251. fit() {
  10252. const {options, ctx} = this;
  10253. if (!options.display) {
  10254. this.width = this.height = 0;
  10255. return;
  10256. }
  10257. const labelOpts = options.labels;
  10258. const labelFont = toFont(labelOpts.font);
  10259. const fontSize = labelFont.size;
  10260. const titleHeight = this._computeTitleHeight();
  10261. const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize);
  10262. let width, height;
  10263. ctx.font = labelFont.string;
  10264. if (this.isHorizontal()) {
  10265. width = this.maxWidth;
  10266. height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  10267. } else {
  10268. height = this.maxHeight;
  10269. width = this._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  10270. }
  10271. this.width = Math.min(width, options.maxWidth || this.maxWidth);
  10272. this.height = Math.min(height, options.maxHeight || this.maxHeight);
  10273. }
  10274. _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
  10275. const {ctx, maxWidth, options: {labels: {padding}}} = this;
  10276. const hitboxes = this.legendHitBoxes = [];
  10277. const lineWidths = this.lineWidths = [0];
  10278. const lineHeight = itemHeight + padding;
  10279. let totalHeight = titleHeight;
  10280. ctx.textAlign = 'left';
  10281. ctx.textBaseline = 'middle';
  10282. let row = -1;
  10283. let top = -lineHeight;
  10284. this.legendItems.forEach((legendItem, i) => {
  10285. const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  10286. if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
  10287. totalHeight += lineHeight;
  10288. lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
  10289. top += lineHeight;
  10290. row++;
  10291. }
  10292. hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight};
  10293. lineWidths[lineWidths.length - 1] += itemWidth + padding;
  10294. });
  10295. return totalHeight;
  10296. }
  10297. _fitCols(titleHeight, fontSize, boxWidth, itemHeight) {
  10298. const {ctx, maxHeight, options: {labels: {padding}}} = this;
  10299. const hitboxes = this.legendHitBoxes = [];
  10300. const columnSizes = this.columnSizes = [];
  10301. const heightLimit = maxHeight - titleHeight;
  10302. let totalWidth = padding;
  10303. let currentColWidth = 0;
  10304. let currentColHeight = 0;
  10305. let left = 0;
  10306. let col = 0;
  10307. this.legendItems.forEach((legendItem, i) => {
  10308. const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  10309. if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
  10310. totalWidth += currentColWidth + padding;
  10311. columnSizes.push({width: currentColWidth, height: currentColHeight});
  10312. left += currentColWidth + padding;
  10313. col++;
  10314. currentColWidth = currentColHeight = 0;
  10315. }
  10316. hitboxes[i] = {left, top: currentColHeight, col, width: itemWidth, height: itemHeight};
  10317. currentColWidth = Math.max(currentColWidth, itemWidth);
  10318. currentColHeight += itemHeight + padding;
  10319. });
  10320. totalWidth += currentColWidth;
  10321. columnSizes.push({width: currentColWidth, height: currentColHeight});
  10322. return totalWidth;
  10323. }
  10324. adjustHitBoxes() {
  10325. if (!this.options.display) {
  10326. return;
  10327. }
  10328. const titleHeight = this._computeTitleHeight();
  10329. const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this;
  10330. const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
  10331. if (this.isHorizontal()) {
  10332. let row = 0;
  10333. let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  10334. for (const hitbox of hitboxes) {
  10335. if (row !== hitbox.row) {
  10336. row = hitbox.row;
  10337. left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  10338. }
  10339. hitbox.top += this.top + titleHeight + padding;
  10340. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
  10341. left += hitbox.width + padding;
  10342. }
  10343. } else {
  10344. let col = 0;
  10345. let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  10346. for (const hitbox of hitboxes) {
  10347. if (hitbox.col !== col) {
  10348. col = hitbox.col;
  10349. top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  10350. }
  10351. hitbox.top = top;
  10352. hitbox.left += this.left + padding;
  10353. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
  10354. top += hitbox.height + padding;
  10355. }
  10356. }
  10357. }
  10358. isHorizontal() {
  10359. return this.options.position === 'top' || this.options.position === 'bottom';
  10360. }
  10361. draw() {
  10362. if (this.options.display) {
  10363. const ctx = this.ctx;
  10364. clipArea(ctx, this);
  10365. this._draw();
  10366. unclipArea(ctx);
  10367. }
  10368. }
  10369. _draw() {
  10370. const {options: opts, columnSizes, lineWidths, ctx} = this;
  10371. const {align, labels: labelOpts} = opts;
  10372. const defaultColor = defaults.color;
  10373. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  10374. const labelFont = toFont(labelOpts.font);
  10375. const {color: fontColor, padding} = labelOpts;
  10376. const fontSize = labelFont.size;
  10377. const halfFontSize = fontSize / 2;
  10378. let cursor;
  10379. this.drawTitle();
  10380. ctx.textAlign = rtlHelper.textAlign('left');
  10381. ctx.textBaseline = 'middle';
  10382. ctx.lineWidth = 0.5;
  10383. ctx.font = labelFont.string;
  10384. const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize);
  10385. const drawLegendBox = function(x, y, legendItem) {
  10386. if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
  10387. return;
  10388. }
  10389. ctx.save();
  10390. const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
  10391. ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
  10392. ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
  10393. ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
  10394. ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
  10395. ctx.lineWidth = lineWidth;
  10396. ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
  10397. ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
  10398. if (labelOpts.usePointStyle) {
  10399. const drawOptions = {
  10400. radius: boxWidth * Math.SQRT2 / 2,
  10401. pointStyle: legendItem.pointStyle,
  10402. rotation: legendItem.rotation,
  10403. borderWidth: lineWidth
  10404. };
  10405. const centerX = rtlHelper.xPlus(x, boxWidth / 2);
  10406. const centerY = y + halfFontSize;
  10407. drawPoint(ctx, drawOptions, centerX, centerY);
  10408. } else {
  10409. const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
  10410. const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
  10411. const borderRadius = toTRBLCorners(legendItem.borderRadius);
  10412. ctx.beginPath();
  10413. if (Object.values(borderRadius).some(v => v !== 0)) {
  10414. addRoundedRectPath(ctx, {
  10415. x: xBoxLeft,
  10416. y: yBoxTop,
  10417. w: boxWidth,
  10418. h: boxHeight,
  10419. radius: borderRadius,
  10420. });
  10421. } else {
  10422. ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
  10423. }
  10424. ctx.fill();
  10425. if (lineWidth !== 0) {
  10426. ctx.stroke();
  10427. }
  10428. }
  10429. ctx.restore();
  10430. };
  10431. const fillText = function(x, y, legendItem) {
  10432. renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {
  10433. strikethrough: legendItem.hidden,
  10434. textAlign: rtlHelper.textAlign(legendItem.textAlign)
  10435. });
  10436. };
  10437. const isHorizontal = this.isHorizontal();
  10438. const titleHeight = this._computeTitleHeight();
  10439. if (isHorizontal) {
  10440. cursor = {
  10441. x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
  10442. y: this.top + padding + titleHeight,
  10443. line: 0
  10444. };
  10445. } else {
  10446. cursor = {
  10447. x: this.left + padding,
  10448. y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
  10449. line: 0
  10450. };
  10451. }
  10452. overrideTextDirection(this.ctx, opts.textDirection);
  10453. const lineHeight = itemHeight + padding;
  10454. this.legendItems.forEach((legendItem, i) => {
  10455. ctx.strokeStyle = legendItem.fontColor || fontColor;
  10456. ctx.fillStyle = legendItem.fontColor || fontColor;
  10457. const textWidth = ctx.measureText(legendItem.text).width;
  10458. const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
  10459. const width = boxWidth + halfFontSize + textWidth;
  10460. let x = cursor.x;
  10461. let y = cursor.y;
  10462. rtlHelper.setWidth(this.width);
  10463. if (isHorizontal) {
  10464. if (i > 0 && x + width + padding > this.right) {
  10465. y = cursor.y += lineHeight;
  10466. cursor.line++;
  10467. x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
  10468. }
  10469. } else if (i > 0 && y + lineHeight > this.bottom) {
  10470. x = cursor.x = x + columnSizes[cursor.line].width + padding;
  10471. cursor.line++;
  10472. y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
  10473. }
  10474. const realX = rtlHelper.x(x);
  10475. drawLegendBox(realX, y, legendItem);
  10476. x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
  10477. fillText(rtlHelper.x(x), y, legendItem);
  10478. if (isHorizontal) {
  10479. cursor.x += width + padding;
  10480. } else {
  10481. cursor.y += lineHeight;
  10482. }
  10483. });
  10484. restoreTextDirection(this.ctx, opts.textDirection);
  10485. }
  10486. drawTitle() {
  10487. const opts = this.options;
  10488. const titleOpts = opts.title;
  10489. const titleFont = toFont(titleOpts.font);
  10490. const titlePadding = toPadding(titleOpts.padding);
  10491. if (!titleOpts.display) {
  10492. return;
  10493. }
  10494. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  10495. const ctx = this.ctx;
  10496. const position = titleOpts.position;
  10497. const halfFontSize = titleFont.size / 2;
  10498. const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
  10499. let y;
  10500. let left = this.left;
  10501. let maxWidth = this.width;
  10502. if (this.isHorizontal()) {
  10503. maxWidth = Math.max(...this.lineWidths);
  10504. y = this.top + topPaddingPlusHalfFontSize;
  10505. left = _alignStartEnd(opts.align, left, this.right - maxWidth);
  10506. } else {
  10507. const maxHeight = this.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0);
  10508. y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
  10509. }
  10510. const x = _alignStartEnd(position, left, left + maxWidth);
  10511. ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
  10512. ctx.textBaseline = 'middle';
  10513. ctx.strokeStyle = titleOpts.color;
  10514. ctx.fillStyle = titleOpts.color;
  10515. ctx.font = titleFont.string;
  10516. renderText(ctx, titleOpts.text, x, y, titleFont);
  10517. }
  10518. _computeTitleHeight() {
  10519. const titleOpts = this.options.title;
  10520. const titleFont = toFont(titleOpts.font);
  10521. const titlePadding = toPadding(titleOpts.padding);
  10522. return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
  10523. }
  10524. _getLegendItemAt(x, y) {
  10525. let i, hitBox, lh;
  10526. if (_isBetween(x, this.left, this.right)
  10527. && _isBetween(y, this.top, this.bottom)) {
  10528. lh = this.legendHitBoxes;
  10529. for (i = 0; i < lh.length; ++i) {
  10530. hitBox = lh[i];
  10531. if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width)
  10532. && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
  10533. return this.legendItems[i];
  10534. }
  10535. }
  10536. }
  10537. return null;
  10538. }
  10539. handleEvent(e) {
  10540. const opts = this.options;
  10541. if (!isListened(e.type, opts)) {
  10542. return;
  10543. }
  10544. const hoveredItem = this._getLegendItemAt(e.x, e.y);
  10545. if (e.type === 'mousemove') {
  10546. const previous = this._hoveredItem;
  10547. const sameItem = itemsEqual(previous, hoveredItem);
  10548. if (previous && !sameItem) {
  10549. callback(opts.onLeave, [e, previous, this], this);
  10550. }
  10551. this._hoveredItem = hoveredItem;
  10552. if (hoveredItem && !sameItem) {
  10553. callback(opts.onHover, [e, hoveredItem, this], this);
  10554. }
  10555. } else if (hoveredItem) {
  10556. callback(opts.onClick, [e, hoveredItem, this], this);
  10557. }
  10558. }
  10559. }
  10560. function isListened(type, opts) {
  10561. if (type === 'mousemove' && (opts.onHover || opts.onLeave)) {
  10562. return true;
  10563. }
  10564. if (opts.onClick && (type === 'click' || type === 'mouseup')) {
  10565. return true;
  10566. }
  10567. return false;
  10568. }
  10569. var plugin_legend = {
  10570. id: 'legend',
  10571. _element: Legend,
  10572. start(chart, _args, options) {
  10573. const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart});
  10574. layouts.configure(chart, legend, options);
  10575. layouts.addBox(chart, legend);
  10576. },
  10577. stop(chart) {
  10578. layouts.removeBox(chart, chart.legend);
  10579. delete chart.legend;
  10580. },
  10581. beforeUpdate(chart, _args, options) {
  10582. const legend = chart.legend;
  10583. layouts.configure(chart, legend, options);
  10584. legend.options = options;
  10585. },
  10586. afterUpdate(chart) {
  10587. const legend = chart.legend;
  10588. legend.buildLabels();
  10589. legend.adjustHitBoxes();
  10590. },
  10591. afterEvent(chart, args) {
  10592. if (!args.replay) {
  10593. chart.legend.handleEvent(args.event);
  10594. }
  10595. },
  10596. defaults: {
  10597. display: true,
  10598. position: 'top',
  10599. align: 'center',
  10600. fullSize: true,
  10601. reverse: false,
  10602. weight: 1000,
  10603. onClick(e, legendItem, legend) {
  10604. const index = legendItem.datasetIndex;
  10605. const ci = legend.chart;
  10606. if (ci.isDatasetVisible(index)) {
  10607. ci.hide(index);
  10608. legendItem.hidden = true;
  10609. } else {
  10610. ci.show(index);
  10611. legendItem.hidden = false;
  10612. }
  10613. },
  10614. onHover: null,
  10615. onLeave: null,
  10616. labels: {
  10617. color: (ctx) => ctx.chart.options.color,
  10618. boxWidth: 40,
  10619. padding: 10,
  10620. generateLabels(chart) {
  10621. const datasets = chart.data.datasets;
  10622. const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options;
  10623. return chart._getSortedDatasetMetas().map((meta) => {
  10624. const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
  10625. const borderWidth = toPadding(style.borderWidth);
  10626. return {
  10627. text: datasets[meta.index].label,
  10628. fillStyle: style.backgroundColor,
  10629. fontColor: color,
  10630. hidden: !meta.visible,
  10631. lineCap: style.borderCapStyle,
  10632. lineDash: style.borderDash,
  10633. lineDashOffset: style.borderDashOffset,
  10634. lineJoin: style.borderJoinStyle,
  10635. lineWidth: (borderWidth.width + borderWidth.height) / 4,
  10636. strokeStyle: style.borderColor,
  10637. pointStyle: pointStyle || style.pointStyle,
  10638. rotation: style.rotation,
  10639. textAlign: textAlign || style.textAlign,
  10640. borderRadius: 0,
  10641. datasetIndex: meta.index
  10642. };
  10643. }, this);
  10644. }
  10645. },
  10646. title: {
  10647. color: (ctx) => ctx.chart.options.color,
  10648. display: false,
  10649. position: 'center',
  10650. text: '',
  10651. }
  10652. },
  10653. descriptors: {
  10654. _scriptable: (name) => !name.startsWith('on'),
  10655. labels: {
  10656. _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name),
  10657. }
  10658. },
  10659. };
  10660. class Title extends Element {
  10661. constructor(config) {
  10662. super();
  10663. this.chart = config.chart;
  10664. this.options = config.options;
  10665. this.ctx = config.ctx;
  10666. this._padding = undefined;
  10667. this.top = undefined;
  10668. this.bottom = undefined;
  10669. this.left = undefined;
  10670. this.right = undefined;
  10671. this.width = undefined;
  10672. this.height = undefined;
  10673. this.position = undefined;
  10674. this.weight = undefined;
  10675. this.fullSize = undefined;
  10676. }
  10677. update(maxWidth, maxHeight) {
  10678. const opts = this.options;
  10679. this.left = 0;
  10680. this.top = 0;
  10681. if (!opts.display) {
  10682. this.width = this.height = this.right = this.bottom = 0;
  10683. return;
  10684. }
  10685. this.width = this.right = maxWidth;
  10686. this.height = this.bottom = maxHeight;
  10687. const lineCount = isArray(opts.text) ? opts.text.length : 1;
  10688. this._padding = toPadding(opts.padding);
  10689. const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
  10690. if (this.isHorizontal()) {
  10691. this.height = textSize;
  10692. } else {
  10693. this.width = textSize;
  10694. }
  10695. }
  10696. isHorizontal() {
  10697. const pos = this.options.position;
  10698. return pos === 'top' || pos === 'bottom';
  10699. }
  10700. _drawArgs(offset) {
  10701. const {top, left, bottom, right, options} = this;
  10702. const align = options.align;
  10703. let rotation = 0;
  10704. let maxWidth, titleX, titleY;
  10705. if (this.isHorizontal()) {
  10706. titleX = _alignStartEnd(align, left, right);
  10707. titleY = top + offset;
  10708. maxWidth = right - left;
  10709. } else {
  10710. if (options.position === 'left') {
  10711. titleX = left + offset;
  10712. titleY = _alignStartEnd(align, bottom, top);
  10713. rotation = PI * -0.5;
  10714. } else {
  10715. titleX = right - offset;
  10716. titleY = _alignStartEnd(align, top, bottom);
  10717. rotation = PI * 0.5;
  10718. }
  10719. maxWidth = bottom - top;
  10720. }
  10721. return {titleX, titleY, maxWidth, rotation};
  10722. }
  10723. draw() {
  10724. const ctx = this.ctx;
  10725. const opts = this.options;
  10726. if (!opts.display) {
  10727. return;
  10728. }
  10729. const fontOpts = toFont(opts.font);
  10730. const lineHeight = fontOpts.lineHeight;
  10731. const offset = lineHeight / 2 + this._padding.top;
  10732. const {titleX, titleY, maxWidth, rotation} = this._drawArgs(offset);
  10733. renderText(ctx, opts.text, 0, 0, fontOpts, {
  10734. color: opts.color,
  10735. maxWidth,
  10736. rotation,
  10737. textAlign: _toLeftRightCenter(opts.align),
  10738. textBaseline: 'middle',
  10739. translation: [titleX, titleY],
  10740. });
  10741. }
  10742. }
  10743. function createTitle(chart, titleOpts) {
  10744. const title = new Title({
  10745. ctx: chart.ctx,
  10746. options: titleOpts,
  10747. chart
  10748. });
  10749. layouts.configure(chart, title, titleOpts);
  10750. layouts.addBox(chart, title);
  10751. chart.titleBlock = title;
  10752. }
  10753. var plugin_title = {
  10754. id: 'title',
  10755. _element: Title,
  10756. start(chart, _args, options) {
  10757. createTitle(chart, options);
  10758. },
  10759. stop(chart) {
  10760. const titleBlock = chart.titleBlock;
  10761. layouts.removeBox(chart, titleBlock);
  10762. delete chart.titleBlock;
  10763. },
  10764. beforeUpdate(chart, _args, options) {
  10765. const title = chart.titleBlock;
  10766. layouts.configure(chart, title, options);
  10767. title.options = options;
  10768. },
  10769. defaults: {
  10770. align: 'center',
  10771. display: false,
  10772. font: {
  10773. weight: 'bold',
  10774. },
  10775. fullSize: true,
  10776. padding: 10,
  10777. position: 'top',
  10778. text: '',
  10779. weight: 2000
  10780. },
  10781. defaultRoutes: {
  10782. color: 'color'
  10783. },
  10784. descriptors: {
  10785. _scriptable: true,
  10786. _indexable: false,
  10787. },
  10788. };
  10789. const map = new WeakMap();
  10790. var plugin_subtitle = {
  10791. id: 'subtitle',
  10792. start(chart, _args, options) {
  10793. const title = new Title({
  10794. ctx: chart.ctx,
  10795. options,
  10796. chart
  10797. });
  10798. layouts.configure(chart, title, options);
  10799. layouts.addBox(chart, title);
  10800. map.set(chart, title);
  10801. },
  10802. stop(chart) {
  10803. layouts.removeBox(chart, map.get(chart));
  10804. map.delete(chart);
  10805. },
  10806. beforeUpdate(chart, _args, options) {
  10807. const title = map.get(chart);
  10808. layouts.configure(chart, title, options);
  10809. title.options = options;
  10810. },
  10811. defaults: {
  10812. align: 'center',
  10813. display: false,
  10814. font: {
  10815. weight: 'normal',
  10816. },
  10817. fullSize: true,
  10818. padding: 0,
  10819. position: 'top',
  10820. text: '',
  10821. weight: 1500
  10822. },
  10823. defaultRoutes: {
  10824. color: 'color'
  10825. },
  10826. descriptors: {
  10827. _scriptable: true,
  10828. _indexable: false,
  10829. },
  10830. };
  10831. const positioners = {
  10832. average(items) {
  10833. if (!items.length) {
  10834. return false;
  10835. }
  10836. let i, len;
  10837. let x = 0;
  10838. let y = 0;
  10839. let count = 0;
  10840. for (i = 0, len = items.length; i < len; ++i) {
  10841. const el = items[i].element;
  10842. if (el && el.hasValue()) {
  10843. const pos = el.tooltipPosition();
  10844. x += pos.x;
  10845. y += pos.y;
  10846. ++count;
  10847. }
  10848. }
  10849. return {
  10850. x: x / count,
  10851. y: y / count
  10852. };
  10853. },
  10854. nearest(items, eventPosition) {
  10855. if (!items.length) {
  10856. return false;
  10857. }
  10858. let x = eventPosition.x;
  10859. let y = eventPosition.y;
  10860. let minDistance = Number.POSITIVE_INFINITY;
  10861. let i, len, nearestElement;
  10862. for (i = 0, len = items.length; i < len; ++i) {
  10863. const el = items[i].element;
  10864. if (el && el.hasValue()) {
  10865. const center = el.getCenterPoint();
  10866. const d = distanceBetweenPoints(eventPosition, center);
  10867. if (d < minDistance) {
  10868. minDistance = d;
  10869. nearestElement = el;
  10870. }
  10871. }
  10872. }
  10873. if (nearestElement) {
  10874. const tp = nearestElement.tooltipPosition();
  10875. x = tp.x;
  10876. y = tp.y;
  10877. }
  10878. return {
  10879. x,
  10880. y
  10881. };
  10882. }
  10883. };
  10884. function pushOrConcat(base, toPush) {
  10885. if (toPush) {
  10886. if (isArray(toPush)) {
  10887. Array.prototype.push.apply(base, toPush);
  10888. } else {
  10889. base.push(toPush);
  10890. }
  10891. }
  10892. return base;
  10893. }
  10894. function splitNewlines(str) {
  10895. if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  10896. return str.split('\n');
  10897. }
  10898. return str;
  10899. }
  10900. function createTooltipItem(chart, item) {
  10901. const {element, datasetIndex, index} = item;
  10902. const controller = chart.getDatasetMeta(datasetIndex).controller;
  10903. const {label, value} = controller.getLabelAndValue(index);
  10904. return {
  10905. chart,
  10906. label,
  10907. parsed: controller.getParsed(index),
  10908. raw: chart.data.datasets[datasetIndex].data[index],
  10909. formattedValue: value,
  10910. dataset: controller.getDataset(),
  10911. dataIndex: index,
  10912. datasetIndex,
  10913. element
  10914. };
  10915. }
  10916. function getTooltipSize(tooltip, options) {
  10917. const ctx = tooltip.chart.ctx;
  10918. const {body, footer, title} = tooltip;
  10919. const {boxWidth, boxHeight} = options;
  10920. const bodyFont = toFont(options.bodyFont);
  10921. const titleFont = toFont(options.titleFont);
  10922. const footerFont = toFont(options.footerFont);
  10923. const titleLineCount = title.length;
  10924. const footerLineCount = footer.length;
  10925. const bodyLineItemCount = body.length;
  10926. const padding = toPadding(options.padding);
  10927. let height = padding.height;
  10928. let width = 0;
  10929. let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
  10930. combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
  10931. if (titleLineCount) {
  10932. height += titleLineCount * titleFont.lineHeight
  10933. + (titleLineCount - 1) * options.titleSpacing
  10934. + options.titleMarginBottom;
  10935. }
  10936. if (combinedBodyLength) {
  10937. const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
  10938. height += bodyLineItemCount * bodyLineHeight
  10939. + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight
  10940. + (combinedBodyLength - 1) * options.bodySpacing;
  10941. }
  10942. if (footerLineCount) {
  10943. height += options.footerMarginTop
  10944. + footerLineCount * footerFont.lineHeight
  10945. + (footerLineCount - 1) * options.footerSpacing;
  10946. }
  10947. let widthPadding = 0;
  10948. const maxLineWidth = function(line) {
  10949. width = Math.max(width, ctx.measureText(line).width + widthPadding);
  10950. };
  10951. ctx.save();
  10952. ctx.font = titleFont.string;
  10953. each(tooltip.title, maxLineWidth);
  10954. ctx.font = bodyFont.string;
  10955. each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
  10956. widthPadding = options.displayColors ? (boxWidth + 2 + options.boxPadding) : 0;
  10957. each(body, (bodyItem) => {
  10958. each(bodyItem.before, maxLineWidth);
  10959. each(bodyItem.lines, maxLineWidth);
  10960. each(bodyItem.after, maxLineWidth);
  10961. });
  10962. widthPadding = 0;
  10963. ctx.font = footerFont.string;
  10964. each(tooltip.footer, maxLineWidth);
  10965. ctx.restore();
  10966. width += padding.width;
  10967. return {width, height};
  10968. }
  10969. function determineYAlign(chart, size) {
  10970. const {y, height} = size;
  10971. if (y < height / 2) {
  10972. return 'top';
  10973. } else if (y > (chart.height - height / 2)) {
  10974. return 'bottom';
  10975. }
  10976. return 'center';
  10977. }
  10978. function doesNotFitWithAlign(xAlign, chart, options, size) {
  10979. const {x, width} = size;
  10980. const caret = options.caretSize + options.caretPadding;
  10981. if (xAlign === 'left' && x + width + caret > chart.width) {
  10982. return true;
  10983. }
  10984. if (xAlign === 'right' && x - width - caret < 0) {
  10985. return true;
  10986. }
  10987. }
  10988. function determineXAlign(chart, options, size, yAlign) {
  10989. const {x, width} = size;
  10990. const {width: chartWidth, chartArea: {left, right}} = chart;
  10991. let xAlign = 'center';
  10992. if (yAlign === 'center') {
  10993. xAlign = x <= (left + right) / 2 ? 'left' : 'right';
  10994. } else if (x <= width / 2) {
  10995. xAlign = 'left';
  10996. } else if (x >= chartWidth - width / 2) {
  10997. xAlign = 'right';
  10998. }
  10999. if (doesNotFitWithAlign(xAlign, chart, options, size)) {
  11000. xAlign = 'center';
  11001. }
  11002. return xAlign;
  11003. }
  11004. function determineAlignment(chart, options, size) {
  11005. const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
  11006. return {
  11007. xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
  11008. yAlign
  11009. };
  11010. }
  11011. function alignX(size, xAlign) {
  11012. let {x, width} = size;
  11013. if (xAlign === 'right') {
  11014. x -= width;
  11015. } else if (xAlign === 'center') {
  11016. x -= (width / 2);
  11017. }
  11018. return x;
  11019. }
  11020. function alignY(size, yAlign, paddingAndSize) {
  11021. let {y, height} = size;
  11022. if (yAlign === 'top') {
  11023. y += paddingAndSize;
  11024. } else if (yAlign === 'bottom') {
  11025. y -= height + paddingAndSize;
  11026. } else {
  11027. y -= (height / 2);
  11028. }
  11029. return y;
  11030. }
  11031. function getBackgroundPoint(options, size, alignment, chart) {
  11032. const {caretSize, caretPadding, cornerRadius} = options;
  11033. const {xAlign, yAlign} = alignment;
  11034. const paddingAndSize = caretSize + caretPadding;
  11035. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
  11036. let x = alignX(size, xAlign);
  11037. const y = alignY(size, yAlign, paddingAndSize);
  11038. if (yAlign === 'center') {
  11039. if (xAlign === 'left') {
  11040. x += paddingAndSize;
  11041. } else if (xAlign === 'right') {
  11042. x -= paddingAndSize;
  11043. }
  11044. } else if (xAlign === 'left') {
  11045. x -= Math.max(topLeft, bottomLeft) + caretSize;
  11046. } else if (xAlign === 'right') {
  11047. x += Math.max(topRight, bottomRight) + caretSize;
  11048. }
  11049. return {
  11050. x: _limitValue(x, 0, chart.width - size.width),
  11051. y: _limitValue(y, 0, chart.height - size.height)
  11052. };
  11053. }
  11054. function getAlignedX(tooltip, align, options) {
  11055. const padding = toPadding(options.padding);
  11056. return align === 'center'
  11057. ? tooltip.x + tooltip.width / 2
  11058. : align === 'right'
  11059. ? tooltip.x + tooltip.width - padding.right
  11060. : tooltip.x + padding.left;
  11061. }
  11062. function getBeforeAfterBodyLines(callback) {
  11063. return pushOrConcat([], splitNewlines(callback));
  11064. }
  11065. function createTooltipContext(parent, tooltip, tooltipItems) {
  11066. return createContext(parent, {
  11067. tooltip,
  11068. tooltipItems,
  11069. type: 'tooltip'
  11070. });
  11071. }
  11072. function overrideCallbacks(callbacks, context) {
  11073. const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  11074. return override ? callbacks.override(override) : callbacks;
  11075. }
  11076. class Tooltip extends Element {
  11077. constructor(config) {
  11078. super();
  11079. this.opacity = 0;
  11080. this._active = [];
  11081. this._eventPosition = undefined;
  11082. this._size = undefined;
  11083. this._cachedAnimations = undefined;
  11084. this._tooltipItems = [];
  11085. this.$animations = undefined;
  11086. this.$context = undefined;
  11087. this.chart = config.chart || config._chart;
  11088. this._chart = this.chart;
  11089. this.options = config.options;
  11090. this.dataPoints = undefined;
  11091. this.title = undefined;
  11092. this.beforeBody = undefined;
  11093. this.body = undefined;
  11094. this.afterBody = undefined;
  11095. this.footer = undefined;
  11096. this.xAlign = undefined;
  11097. this.yAlign = undefined;
  11098. this.x = undefined;
  11099. this.y = undefined;
  11100. this.height = undefined;
  11101. this.width = undefined;
  11102. this.caretX = undefined;
  11103. this.caretY = undefined;
  11104. this.labelColors = undefined;
  11105. this.labelPointStyles = undefined;
  11106. this.labelTextColors = undefined;
  11107. }
  11108. initialize(options) {
  11109. this.options = options;
  11110. this._cachedAnimations = undefined;
  11111. this.$context = undefined;
  11112. }
  11113. _resolveAnimations() {
  11114. const cached = this._cachedAnimations;
  11115. if (cached) {
  11116. return cached;
  11117. }
  11118. const chart = this.chart;
  11119. const options = this.options.setContext(this.getContext());
  11120. const opts = options.enabled && chart.options.animation && options.animations;
  11121. const animations = new Animations(this.chart, opts);
  11122. if (opts._cacheable) {
  11123. this._cachedAnimations = Object.freeze(animations);
  11124. }
  11125. return animations;
  11126. }
  11127. getContext() {
  11128. return this.$context ||
  11129. (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
  11130. }
  11131. getTitle(context, options) {
  11132. const {callbacks} = options;
  11133. const beforeTitle = callbacks.beforeTitle.apply(this, [context]);
  11134. const title = callbacks.title.apply(this, [context]);
  11135. const afterTitle = callbacks.afterTitle.apply(this, [context]);
  11136. let lines = [];
  11137. lines = pushOrConcat(lines, splitNewlines(beforeTitle));
  11138. lines = pushOrConcat(lines, splitNewlines(title));
  11139. lines = pushOrConcat(lines, splitNewlines(afterTitle));
  11140. return lines;
  11141. }
  11142. getBeforeBody(tooltipItems, options) {
  11143. return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems]));
  11144. }
  11145. getBody(tooltipItems, options) {
  11146. const {callbacks} = options;
  11147. const bodyItems = [];
  11148. each(tooltipItems, (context) => {
  11149. const bodyItem = {
  11150. before: [],
  11151. lines: [],
  11152. after: []
  11153. };
  11154. const scoped = overrideCallbacks(callbacks, context);
  11155. pushOrConcat(bodyItem.before, splitNewlines(scoped.beforeLabel.call(this, context)));
  11156. pushOrConcat(bodyItem.lines, scoped.label.call(this, context));
  11157. pushOrConcat(bodyItem.after, splitNewlines(scoped.afterLabel.call(this, context)));
  11158. bodyItems.push(bodyItem);
  11159. });
  11160. return bodyItems;
  11161. }
  11162. getAfterBody(tooltipItems, options) {
  11163. return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems]));
  11164. }
  11165. getFooter(tooltipItems, options) {
  11166. const {callbacks} = options;
  11167. const beforeFooter = callbacks.beforeFooter.apply(this, [tooltipItems]);
  11168. const footer = callbacks.footer.apply(this, [tooltipItems]);
  11169. const afterFooter = callbacks.afterFooter.apply(this, [tooltipItems]);
  11170. let lines = [];
  11171. lines = pushOrConcat(lines, splitNewlines(beforeFooter));
  11172. lines = pushOrConcat(lines, splitNewlines(footer));
  11173. lines = pushOrConcat(lines, splitNewlines(afterFooter));
  11174. return lines;
  11175. }
  11176. _createItems(options) {
  11177. const active = this._active;
  11178. const data = this.chart.data;
  11179. const labelColors = [];
  11180. const labelPointStyles = [];
  11181. const labelTextColors = [];
  11182. let tooltipItems = [];
  11183. let i, len;
  11184. for (i = 0, len = active.length; i < len; ++i) {
  11185. tooltipItems.push(createTooltipItem(this.chart, active[i]));
  11186. }
  11187. if (options.filter) {
  11188. tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data));
  11189. }
  11190. if (options.itemSort) {
  11191. tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data));
  11192. }
  11193. each(tooltipItems, (context) => {
  11194. const scoped = overrideCallbacks(options.callbacks, context);
  11195. labelColors.push(scoped.labelColor.call(this, context));
  11196. labelPointStyles.push(scoped.labelPointStyle.call(this, context));
  11197. labelTextColors.push(scoped.labelTextColor.call(this, context));
  11198. });
  11199. this.labelColors = labelColors;
  11200. this.labelPointStyles = labelPointStyles;
  11201. this.labelTextColors = labelTextColors;
  11202. this.dataPoints = tooltipItems;
  11203. return tooltipItems;
  11204. }
  11205. update(changed, replay) {
  11206. const options = this.options.setContext(this.getContext());
  11207. const active = this._active;
  11208. let properties;
  11209. let tooltipItems = [];
  11210. if (!active.length) {
  11211. if (this.opacity !== 0) {
  11212. properties = {
  11213. opacity: 0
  11214. };
  11215. }
  11216. } else {
  11217. const position = positioners[options.position].call(this, active, this._eventPosition);
  11218. tooltipItems = this._createItems(options);
  11219. this.title = this.getTitle(tooltipItems, options);
  11220. this.beforeBody = this.getBeforeBody(tooltipItems, options);
  11221. this.body = this.getBody(tooltipItems, options);
  11222. this.afterBody = this.getAfterBody(tooltipItems, options);
  11223. this.footer = this.getFooter(tooltipItems, options);
  11224. const size = this._size = getTooltipSize(this, options);
  11225. const positionAndSize = Object.assign({}, position, size);
  11226. const alignment = determineAlignment(this.chart, options, positionAndSize);
  11227. const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
  11228. this.xAlign = alignment.xAlign;
  11229. this.yAlign = alignment.yAlign;
  11230. properties = {
  11231. opacity: 1,
  11232. x: backgroundPoint.x,
  11233. y: backgroundPoint.y,
  11234. width: size.width,
  11235. height: size.height,
  11236. caretX: position.x,
  11237. caretY: position.y
  11238. };
  11239. }
  11240. this._tooltipItems = tooltipItems;
  11241. this.$context = undefined;
  11242. if (properties) {
  11243. this._resolveAnimations().update(this, properties);
  11244. }
  11245. if (changed && options.external) {
  11246. options.external.call(this, {chart: this.chart, tooltip: this, replay});
  11247. }
  11248. }
  11249. drawCaret(tooltipPoint, ctx, size, options) {
  11250. const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
  11251. ctx.lineTo(caretPosition.x1, caretPosition.y1);
  11252. ctx.lineTo(caretPosition.x2, caretPosition.y2);
  11253. ctx.lineTo(caretPosition.x3, caretPosition.y3);
  11254. }
  11255. getCaretPosition(tooltipPoint, size, options) {
  11256. const {xAlign, yAlign} = this;
  11257. const {caretSize, cornerRadius} = options;
  11258. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
  11259. const {x: ptX, y: ptY} = tooltipPoint;
  11260. const {width, height} = size;
  11261. let x1, x2, x3, y1, y2, y3;
  11262. if (yAlign === 'center') {
  11263. y2 = ptY + (height / 2);
  11264. if (xAlign === 'left') {
  11265. x1 = ptX;
  11266. x2 = x1 - caretSize;
  11267. y1 = y2 + caretSize;
  11268. y3 = y2 - caretSize;
  11269. } else {
  11270. x1 = ptX + width;
  11271. x2 = x1 + caretSize;
  11272. y1 = y2 - caretSize;
  11273. y3 = y2 + caretSize;
  11274. }
  11275. x3 = x1;
  11276. } else {
  11277. if (xAlign === 'left') {
  11278. x2 = ptX + Math.max(topLeft, bottomLeft) + (caretSize);
  11279. } else if (xAlign === 'right') {
  11280. x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
  11281. } else {
  11282. x2 = this.caretX;
  11283. }
  11284. if (yAlign === 'top') {
  11285. y1 = ptY;
  11286. y2 = y1 - caretSize;
  11287. x1 = x2 - caretSize;
  11288. x3 = x2 + caretSize;
  11289. } else {
  11290. y1 = ptY + height;
  11291. y2 = y1 + caretSize;
  11292. x1 = x2 + caretSize;
  11293. x3 = x2 - caretSize;
  11294. }
  11295. y3 = y1;
  11296. }
  11297. return {x1, x2, x3, y1, y2, y3};
  11298. }
  11299. drawTitle(pt, ctx, options) {
  11300. const title = this.title;
  11301. const length = title.length;
  11302. let titleFont, titleSpacing, i;
  11303. if (length) {
  11304. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11305. pt.x = getAlignedX(this, options.titleAlign, options);
  11306. ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
  11307. ctx.textBaseline = 'middle';
  11308. titleFont = toFont(options.titleFont);
  11309. titleSpacing = options.titleSpacing;
  11310. ctx.fillStyle = options.titleColor;
  11311. ctx.font = titleFont.string;
  11312. for (i = 0; i < length; ++i) {
  11313. ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
  11314. pt.y += titleFont.lineHeight + titleSpacing;
  11315. if (i + 1 === length) {
  11316. pt.y += options.titleMarginBottom - titleSpacing;
  11317. }
  11318. }
  11319. }
  11320. }
  11321. _drawColorBox(ctx, pt, i, rtlHelper, options) {
  11322. const labelColors = this.labelColors[i];
  11323. const labelPointStyle = this.labelPointStyles[i];
  11324. const {boxHeight, boxWidth, boxPadding} = options;
  11325. const bodyFont = toFont(options.bodyFont);
  11326. const colorX = getAlignedX(this, 'left', options);
  11327. const rtlColorX = rtlHelper.x(colorX);
  11328. const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
  11329. const colorY = pt.y + yOffSet;
  11330. if (options.usePointStyle) {
  11331. const drawOptions = {
  11332. radius: Math.min(boxWidth, boxHeight) / 2,
  11333. pointStyle: labelPointStyle.pointStyle,
  11334. rotation: labelPointStyle.rotation,
  11335. borderWidth: 1
  11336. };
  11337. const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
  11338. const centerY = colorY + boxHeight / 2;
  11339. ctx.strokeStyle = options.multiKeyBackground;
  11340. ctx.fillStyle = options.multiKeyBackground;
  11341. drawPoint(ctx, drawOptions, centerX, centerY);
  11342. ctx.strokeStyle = labelColors.borderColor;
  11343. ctx.fillStyle = labelColors.backgroundColor;
  11344. drawPoint(ctx, drawOptions, centerX, centerY);
  11345. } else {
  11346. ctx.lineWidth = labelColors.borderWidth || 1;
  11347. ctx.strokeStyle = labelColors.borderColor;
  11348. ctx.setLineDash(labelColors.borderDash || []);
  11349. ctx.lineDashOffset = labelColors.borderDashOffset || 0;
  11350. const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth - boxPadding);
  11351. const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - boxPadding - 2);
  11352. const borderRadius = toTRBLCorners(labelColors.borderRadius);
  11353. if (Object.values(borderRadius).some(v => v !== 0)) {
  11354. ctx.beginPath();
  11355. ctx.fillStyle = options.multiKeyBackground;
  11356. addRoundedRectPath(ctx, {
  11357. x: outerX,
  11358. y: colorY,
  11359. w: boxWidth,
  11360. h: boxHeight,
  11361. radius: borderRadius,
  11362. });
  11363. ctx.fill();
  11364. ctx.stroke();
  11365. ctx.fillStyle = labelColors.backgroundColor;
  11366. ctx.beginPath();
  11367. addRoundedRectPath(ctx, {
  11368. x: innerX,
  11369. y: colorY + 1,
  11370. w: boxWidth - 2,
  11371. h: boxHeight - 2,
  11372. radius: borderRadius,
  11373. });
  11374. ctx.fill();
  11375. } else {
  11376. ctx.fillStyle = options.multiKeyBackground;
  11377. ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
  11378. ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
  11379. ctx.fillStyle = labelColors.backgroundColor;
  11380. ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
  11381. }
  11382. }
  11383. ctx.fillStyle = this.labelTextColors[i];
  11384. }
  11385. drawBody(pt, ctx, options) {
  11386. const {body} = this;
  11387. const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth, boxPadding} = options;
  11388. const bodyFont = toFont(options.bodyFont);
  11389. let bodyLineHeight = bodyFont.lineHeight;
  11390. let xLinePadding = 0;
  11391. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11392. const fillLineOfText = function(line) {
  11393. ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
  11394. pt.y += bodyLineHeight + bodySpacing;
  11395. };
  11396. const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
  11397. let bodyItem, textColor, lines, i, j, ilen, jlen;
  11398. ctx.textAlign = bodyAlign;
  11399. ctx.textBaseline = 'middle';
  11400. ctx.font = bodyFont.string;
  11401. pt.x = getAlignedX(this, bodyAlignForCalculation, options);
  11402. ctx.fillStyle = options.bodyColor;
  11403. each(this.beforeBody, fillLineOfText);
  11404. xLinePadding = displayColors && bodyAlignForCalculation !== 'right'
  11405. ? bodyAlign === 'center' ? (boxWidth / 2 + boxPadding) : (boxWidth + 2 + boxPadding)
  11406. : 0;
  11407. for (i = 0, ilen = body.length; i < ilen; ++i) {
  11408. bodyItem = body[i];
  11409. textColor = this.labelTextColors[i];
  11410. ctx.fillStyle = textColor;
  11411. each(bodyItem.before, fillLineOfText);
  11412. lines = bodyItem.lines;
  11413. if (displayColors && lines.length) {
  11414. this._drawColorBox(ctx, pt, i, rtlHelper, options);
  11415. bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
  11416. }
  11417. for (j = 0, jlen = lines.length; j < jlen; ++j) {
  11418. fillLineOfText(lines[j]);
  11419. bodyLineHeight = bodyFont.lineHeight;
  11420. }
  11421. each(bodyItem.after, fillLineOfText);
  11422. }
  11423. xLinePadding = 0;
  11424. bodyLineHeight = bodyFont.lineHeight;
  11425. each(this.afterBody, fillLineOfText);
  11426. pt.y -= bodySpacing;
  11427. }
  11428. drawFooter(pt, ctx, options) {
  11429. const footer = this.footer;
  11430. const length = footer.length;
  11431. let footerFont, i;
  11432. if (length) {
  11433. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11434. pt.x = getAlignedX(this, options.footerAlign, options);
  11435. pt.y += options.footerMarginTop;
  11436. ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
  11437. ctx.textBaseline = 'middle';
  11438. footerFont = toFont(options.footerFont);
  11439. ctx.fillStyle = options.footerColor;
  11440. ctx.font = footerFont.string;
  11441. for (i = 0; i < length; ++i) {
  11442. ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
  11443. pt.y += footerFont.lineHeight + options.footerSpacing;
  11444. }
  11445. }
  11446. }
  11447. drawBackground(pt, ctx, tooltipSize, options) {
  11448. const {xAlign, yAlign} = this;
  11449. const {x, y} = pt;
  11450. const {width, height} = tooltipSize;
  11451. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(options.cornerRadius);
  11452. ctx.fillStyle = options.backgroundColor;
  11453. ctx.strokeStyle = options.borderColor;
  11454. ctx.lineWidth = options.borderWidth;
  11455. ctx.beginPath();
  11456. ctx.moveTo(x + topLeft, y);
  11457. if (yAlign === 'top') {
  11458. this.drawCaret(pt, ctx, tooltipSize, options);
  11459. }
  11460. ctx.lineTo(x + width - topRight, y);
  11461. ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
  11462. if (yAlign === 'center' && xAlign === 'right') {
  11463. this.drawCaret(pt, ctx, tooltipSize, options);
  11464. }
  11465. ctx.lineTo(x + width, y + height - bottomRight);
  11466. ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
  11467. if (yAlign === 'bottom') {
  11468. this.drawCaret(pt, ctx, tooltipSize, options);
  11469. }
  11470. ctx.lineTo(x + bottomLeft, y + height);
  11471. ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
  11472. if (yAlign === 'center' && xAlign === 'left') {
  11473. this.drawCaret(pt, ctx, tooltipSize, options);
  11474. }
  11475. ctx.lineTo(x, y + topLeft);
  11476. ctx.quadraticCurveTo(x, y, x + topLeft, y);
  11477. ctx.closePath();
  11478. ctx.fill();
  11479. if (options.borderWidth > 0) {
  11480. ctx.stroke();
  11481. }
  11482. }
  11483. _updateAnimationTarget(options) {
  11484. const chart = this.chart;
  11485. const anims = this.$animations;
  11486. const animX = anims && anims.x;
  11487. const animY = anims && anims.y;
  11488. if (animX || animY) {
  11489. const position = positioners[options.position].call(this, this._active, this._eventPosition);
  11490. if (!position) {
  11491. return;
  11492. }
  11493. const size = this._size = getTooltipSize(this, options);
  11494. const positionAndSize = Object.assign({}, position, this._size);
  11495. const alignment = determineAlignment(chart, options, positionAndSize);
  11496. const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
  11497. if (animX._to !== point.x || animY._to !== point.y) {
  11498. this.xAlign = alignment.xAlign;
  11499. this.yAlign = alignment.yAlign;
  11500. this.width = size.width;
  11501. this.height = size.height;
  11502. this.caretX = position.x;
  11503. this.caretY = position.y;
  11504. this._resolveAnimations().update(this, point);
  11505. }
  11506. }
  11507. }
  11508. draw(ctx) {
  11509. const options = this.options.setContext(this.getContext());
  11510. let opacity = this.opacity;
  11511. if (!opacity) {
  11512. return;
  11513. }
  11514. this._updateAnimationTarget(options);
  11515. const tooltipSize = {
  11516. width: this.width,
  11517. height: this.height
  11518. };
  11519. const pt = {
  11520. x: this.x,
  11521. y: this.y
  11522. };
  11523. opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
  11524. const padding = toPadding(options.padding);
  11525. const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
  11526. if (options.enabled && hasTooltipContent) {
  11527. ctx.save();
  11528. ctx.globalAlpha = opacity;
  11529. this.drawBackground(pt, ctx, tooltipSize, options);
  11530. overrideTextDirection(ctx, options.textDirection);
  11531. pt.y += padding.top;
  11532. this.drawTitle(pt, ctx, options);
  11533. this.drawBody(pt, ctx, options);
  11534. this.drawFooter(pt, ctx, options);
  11535. restoreTextDirection(ctx, options.textDirection);
  11536. ctx.restore();
  11537. }
  11538. }
  11539. getActiveElements() {
  11540. return this._active || [];
  11541. }
  11542. setActiveElements(activeElements, eventPosition) {
  11543. const lastActive = this._active;
  11544. const active = activeElements.map(({datasetIndex, index}) => {
  11545. const meta = this.chart.getDatasetMeta(datasetIndex);
  11546. if (!meta) {
  11547. throw new Error('Cannot find a dataset at index ' + datasetIndex);
  11548. }
  11549. return {
  11550. datasetIndex,
  11551. element: meta.data[index],
  11552. index,
  11553. };
  11554. });
  11555. const changed = !_elementsEqual(lastActive, active);
  11556. const positionChanged = this._positionChanged(active, eventPosition);
  11557. if (changed || positionChanged) {
  11558. this._active = active;
  11559. this._eventPosition = eventPosition;
  11560. this._ignoreReplayEvents = true;
  11561. this.update(true);
  11562. }
  11563. }
  11564. handleEvent(e, replay, inChartArea = true) {
  11565. if (replay && this._ignoreReplayEvents) {
  11566. return false;
  11567. }
  11568. this._ignoreReplayEvents = false;
  11569. const options = this.options;
  11570. const lastActive = this._active || [];
  11571. const active = this._getActiveElements(e, lastActive, replay, inChartArea);
  11572. const positionChanged = this._positionChanged(active, e);
  11573. const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
  11574. if (changed) {
  11575. this._active = active;
  11576. if (options.enabled || options.external) {
  11577. this._eventPosition = {
  11578. x: e.x,
  11579. y: e.y
  11580. };
  11581. this.update(true, replay);
  11582. }
  11583. }
  11584. return changed;
  11585. }
  11586. _getActiveElements(e, lastActive, replay, inChartArea) {
  11587. const options = this.options;
  11588. if (e.type === 'mouseout') {
  11589. return [];
  11590. }
  11591. if (!inChartArea) {
  11592. return lastActive;
  11593. }
  11594. const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
  11595. if (options.reverse) {
  11596. active.reverse();
  11597. }
  11598. return active;
  11599. }
  11600. _positionChanged(active, e) {
  11601. const {caretX, caretY, options} = this;
  11602. const position = positioners[options.position].call(this, active, e);
  11603. return position !== false && (caretX !== position.x || caretY !== position.y);
  11604. }
  11605. }
  11606. Tooltip.positioners = positioners;
  11607. var plugin_tooltip = {
  11608. id: 'tooltip',
  11609. _element: Tooltip,
  11610. positioners,
  11611. afterInit(chart, _args, options) {
  11612. if (options) {
  11613. chart.tooltip = new Tooltip({chart, options});
  11614. }
  11615. },
  11616. beforeUpdate(chart, _args, options) {
  11617. if (chart.tooltip) {
  11618. chart.tooltip.initialize(options);
  11619. }
  11620. },
  11621. reset(chart, _args, options) {
  11622. if (chart.tooltip) {
  11623. chart.tooltip.initialize(options);
  11624. }
  11625. },
  11626. afterDraw(chart) {
  11627. const tooltip = chart.tooltip;
  11628. const args = {
  11629. tooltip
  11630. };
  11631. if (chart.notifyPlugins('beforeTooltipDraw', args) === false) {
  11632. return;
  11633. }
  11634. if (tooltip) {
  11635. tooltip.draw(chart.ctx);
  11636. }
  11637. chart.notifyPlugins('afterTooltipDraw', args);
  11638. },
  11639. afterEvent(chart, args) {
  11640. if (chart.tooltip) {
  11641. const useFinalPosition = args.replay;
  11642. if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
  11643. args.changed = true;
  11644. }
  11645. }
  11646. },
  11647. defaults: {
  11648. enabled: true,
  11649. external: null,
  11650. position: 'average',
  11651. backgroundColor: 'rgba(0,0,0,0.8)',
  11652. titleColor: '#fff',
  11653. titleFont: {
  11654. weight: 'bold',
  11655. },
  11656. titleSpacing: 2,
  11657. titleMarginBottom: 6,
  11658. titleAlign: 'left',
  11659. bodyColor: '#fff',
  11660. bodySpacing: 2,
  11661. bodyFont: {
  11662. },
  11663. bodyAlign: 'left',
  11664. footerColor: '#fff',
  11665. footerSpacing: 2,
  11666. footerMarginTop: 6,
  11667. footerFont: {
  11668. weight: 'bold',
  11669. },
  11670. footerAlign: 'left',
  11671. padding: 6,
  11672. caretPadding: 2,
  11673. caretSize: 5,
  11674. cornerRadius: 6,
  11675. boxHeight: (ctx, opts) => opts.bodyFont.size,
  11676. boxWidth: (ctx, opts) => opts.bodyFont.size,
  11677. multiKeyBackground: '#fff',
  11678. displayColors: true,
  11679. boxPadding: 0,
  11680. borderColor: 'rgba(0,0,0,0)',
  11681. borderWidth: 0,
  11682. animation: {
  11683. duration: 400,
  11684. easing: 'easeOutQuart',
  11685. },
  11686. animations: {
  11687. numbers: {
  11688. type: 'number',
  11689. properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
  11690. },
  11691. opacity: {
  11692. easing: 'linear',
  11693. duration: 200
  11694. }
  11695. },
  11696. callbacks: {
  11697. beforeTitle: noop,
  11698. title(tooltipItems) {
  11699. if (tooltipItems.length > 0) {
  11700. const item = tooltipItems[0];
  11701. const labels = item.chart.data.labels;
  11702. const labelCount = labels ? labels.length : 0;
  11703. if (this && this.options && this.options.mode === 'dataset') {
  11704. return item.dataset.label || '';
  11705. } else if (item.label) {
  11706. return item.label;
  11707. } else if (labelCount > 0 && item.dataIndex < labelCount) {
  11708. return labels[item.dataIndex];
  11709. }
  11710. }
  11711. return '';
  11712. },
  11713. afterTitle: noop,
  11714. beforeBody: noop,
  11715. beforeLabel: noop,
  11716. label(tooltipItem) {
  11717. if (this && this.options && this.options.mode === 'dataset') {
  11718. return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
  11719. }
  11720. let label = tooltipItem.dataset.label || '';
  11721. if (label) {
  11722. label += ': ';
  11723. }
  11724. const value = tooltipItem.formattedValue;
  11725. if (!isNullOrUndef(value)) {
  11726. label += value;
  11727. }
  11728. return label;
  11729. },
  11730. labelColor(tooltipItem) {
  11731. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  11732. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  11733. return {
  11734. borderColor: options.borderColor,
  11735. backgroundColor: options.backgroundColor,
  11736. borderWidth: options.borderWidth,
  11737. borderDash: options.borderDash,
  11738. borderDashOffset: options.borderDashOffset,
  11739. borderRadius: 0,
  11740. };
  11741. },
  11742. labelTextColor() {
  11743. return this.options.bodyColor;
  11744. },
  11745. labelPointStyle(tooltipItem) {
  11746. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  11747. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  11748. return {
  11749. pointStyle: options.pointStyle,
  11750. rotation: options.rotation,
  11751. };
  11752. },
  11753. afterLabel: noop,
  11754. afterBody: noop,
  11755. beforeFooter: noop,
  11756. footer: noop,
  11757. afterFooter: noop
  11758. }
  11759. },
  11760. defaultRoutes: {
  11761. bodyFont: 'font',
  11762. footerFont: 'font',
  11763. titleFont: 'font'
  11764. },
  11765. descriptors: {
  11766. _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external',
  11767. _indexable: false,
  11768. callbacks: {
  11769. _scriptable: false,
  11770. _indexable: false,
  11771. },
  11772. animation: {
  11773. _fallback: false
  11774. },
  11775. animations: {
  11776. _fallback: 'animation'
  11777. }
  11778. },
  11779. additionalOptionScopes: ['interaction']
  11780. };
  11781. var plugins = /*#__PURE__*/Object.freeze({
  11782. __proto__: null,
  11783. Decimation: plugin_decimation,
  11784. Filler: plugin_filler,
  11785. Legend: plugin_legend,
  11786. SubTitle: plugin_subtitle,
  11787. Title: plugin_title,
  11788. Tooltip: plugin_tooltip
  11789. });
  11790. const addIfString = (labels, raw, index, addedLabels) => {
  11791. if (typeof raw === 'string') {
  11792. index = labels.push(raw) - 1;
  11793. addedLabels.unshift({index, label: raw});
  11794. } else if (isNaN(raw)) {
  11795. index = null;
  11796. }
  11797. return index;
  11798. };
  11799. function findOrAddLabel(labels, raw, index, addedLabels) {
  11800. const first = labels.indexOf(raw);
  11801. if (first === -1) {
  11802. return addIfString(labels, raw, index, addedLabels);
  11803. }
  11804. const last = labels.lastIndexOf(raw);
  11805. return first !== last ? index : first;
  11806. }
  11807. const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);
  11808. class CategoryScale extends Scale {
  11809. constructor(cfg) {
  11810. super(cfg);
  11811. this._startValue = undefined;
  11812. this._valueRange = 0;
  11813. this._addedLabels = [];
  11814. }
  11815. init(scaleOptions) {
  11816. const added = this._addedLabels;
  11817. if (added.length) {
  11818. const labels = this.getLabels();
  11819. for (const {index, label} of added) {
  11820. if (labels[index] === label) {
  11821. labels.splice(index, 1);
  11822. }
  11823. }
  11824. this._addedLabels = [];
  11825. }
  11826. super.init(scaleOptions);
  11827. }
  11828. parse(raw, index) {
  11829. if (isNullOrUndef(raw)) {
  11830. return null;
  11831. }
  11832. const labels = this.getLabels();
  11833. index = isFinite(index) && labels[index] === raw ? index
  11834. : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
  11835. return validIndex(index, labels.length - 1);
  11836. }
  11837. determineDataLimits() {
  11838. const {minDefined, maxDefined} = this.getUserBounds();
  11839. let {min, max} = this.getMinMax(true);
  11840. if (this.options.bounds === 'ticks') {
  11841. if (!minDefined) {
  11842. min = 0;
  11843. }
  11844. if (!maxDefined) {
  11845. max = this.getLabels().length - 1;
  11846. }
  11847. }
  11848. this.min = min;
  11849. this.max = max;
  11850. }
  11851. buildTicks() {
  11852. const min = this.min;
  11853. const max = this.max;
  11854. const offset = this.options.offset;
  11855. const ticks = [];
  11856. let labels = this.getLabels();
  11857. labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1);
  11858. this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
  11859. this._startValue = this.min - (offset ? 0.5 : 0);
  11860. for (let value = min; value <= max; value++) {
  11861. ticks.push({value});
  11862. }
  11863. return ticks;
  11864. }
  11865. getLabelForValue(value) {
  11866. const labels = this.getLabels();
  11867. if (value >= 0 && value < labels.length) {
  11868. return labels[value];
  11869. }
  11870. return value;
  11871. }
  11872. configure() {
  11873. super.configure();
  11874. if (!this.isHorizontal()) {
  11875. this._reversePixels = !this._reversePixels;
  11876. }
  11877. }
  11878. getPixelForValue(value) {
  11879. if (typeof value !== 'number') {
  11880. value = this.parse(value);
  11881. }
  11882. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  11883. }
  11884. getPixelForTick(index) {
  11885. const ticks = this.ticks;
  11886. if (index < 0 || index > ticks.length - 1) {
  11887. return null;
  11888. }
  11889. return this.getPixelForValue(ticks[index].value);
  11890. }
  11891. getValueForPixel(pixel) {
  11892. return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
  11893. }
  11894. getBasePixel() {
  11895. return this.bottom;
  11896. }
  11897. }
  11898. CategoryScale.id = 'category';
  11899. CategoryScale.defaults = {
  11900. ticks: {
  11901. callback: CategoryScale.prototype.getLabelForValue
  11902. }
  11903. };
  11904. function generateTicks$1(generationOptions, dataRange) {
  11905. const ticks = [];
  11906. const MIN_SPACING = 1e-14;
  11907. const {bounds, step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions;
  11908. const unit = step || 1;
  11909. const maxSpaces = maxTicks - 1;
  11910. const {min: rmin, max: rmax} = dataRange;
  11911. const minDefined = !isNullOrUndef(min);
  11912. const maxDefined = !isNullOrUndef(max);
  11913. const countDefined = !isNullOrUndef(count);
  11914. const minSpacing = (rmax - rmin) / (maxDigits + 1);
  11915. let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
  11916. let factor, niceMin, niceMax, numSpaces;
  11917. if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
  11918. return [{value: rmin}, {value: rmax}];
  11919. }
  11920. numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
  11921. if (numSpaces > maxSpaces) {
  11922. spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
  11923. }
  11924. if (!isNullOrUndef(precision)) {
  11925. factor = Math.pow(10, precision);
  11926. spacing = Math.ceil(spacing * factor) / factor;
  11927. }
  11928. if (bounds === 'ticks') {
  11929. niceMin = Math.floor(rmin / spacing) * spacing;
  11930. niceMax = Math.ceil(rmax / spacing) * spacing;
  11931. } else {
  11932. niceMin = rmin;
  11933. niceMax = rmax;
  11934. }
  11935. if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
  11936. numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
  11937. spacing = (max - min) / numSpaces;
  11938. niceMin = min;
  11939. niceMax = max;
  11940. } else if (countDefined) {
  11941. niceMin = minDefined ? min : niceMin;
  11942. niceMax = maxDefined ? max : niceMax;
  11943. numSpaces = count - 1;
  11944. spacing = (niceMax - niceMin) / numSpaces;
  11945. } else {
  11946. numSpaces = (niceMax - niceMin) / spacing;
  11947. if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  11948. numSpaces = Math.round(numSpaces);
  11949. } else {
  11950. numSpaces = Math.ceil(numSpaces);
  11951. }
  11952. }
  11953. const decimalPlaces = Math.max(
  11954. _decimalPlaces(spacing),
  11955. _decimalPlaces(niceMin)
  11956. );
  11957. factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
  11958. niceMin = Math.round(niceMin * factor) / factor;
  11959. niceMax = Math.round(niceMax * factor) / factor;
  11960. let j = 0;
  11961. if (minDefined) {
  11962. if (includeBounds && niceMin !== min) {
  11963. ticks.push({value: min});
  11964. if (niceMin < min) {
  11965. j++;
  11966. }
  11967. if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
  11968. j++;
  11969. }
  11970. } else if (niceMin < min) {
  11971. j++;
  11972. }
  11973. }
  11974. for (; j < numSpaces; ++j) {
  11975. ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor});
  11976. }
  11977. if (maxDefined && includeBounds && niceMax !== max) {
  11978. if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
  11979. ticks[ticks.length - 1].value = max;
  11980. } else {
  11981. ticks.push({value: max});
  11982. }
  11983. } else if (!maxDefined || niceMax === max) {
  11984. ticks.push({value: niceMax});
  11985. }
  11986. return ticks;
  11987. }
  11988. function relativeLabelSize(value, minSpacing, {horizontal, minRotation}) {
  11989. const rad = toRadians(minRotation);
  11990. const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
  11991. const length = 0.75 * minSpacing * ('' + value).length;
  11992. return Math.min(minSpacing / ratio, length);
  11993. }
  11994. class LinearScaleBase extends Scale {
  11995. constructor(cfg) {
  11996. super(cfg);
  11997. this.start = undefined;
  11998. this.end = undefined;
  11999. this._startValue = undefined;
  12000. this._endValue = undefined;
  12001. this._valueRange = 0;
  12002. }
  12003. parse(raw, index) {
  12004. if (isNullOrUndef(raw)) {
  12005. return null;
  12006. }
  12007. if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
  12008. return null;
  12009. }
  12010. return +raw;
  12011. }
  12012. handleTickRangeOptions() {
  12013. const {beginAtZero} = this.options;
  12014. const {minDefined, maxDefined} = this.getUserBounds();
  12015. let {min, max} = this;
  12016. const setMin = v => (min = minDefined ? min : v);
  12017. const setMax = v => (max = maxDefined ? max : v);
  12018. if (beginAtZero) {
  12019. const minSign = sign(min);
  12020. const maxSign = sign(max);
  12021. if (minSign < 0 && maxSign < 0) {
  12022. setMax(0);
  12023. } else if (minSign > 0 && maxSign > 0) {
  12024. setMin(0);
  12025. }
  12026. }
  12027. if (min === max) {
  12028. let offset = 1;
  12029. if (max >= Number.MAX_SAFE_INTEGER || min <= Number.MIN_SAFE_INTEGER) {
  12030. offset = Math.abs(max * 0.05);
  12031. }
  12032. setMax(max + offset);
  12033. if (!beginAtZero) {
  12034. setMin(min - offset);
  12035. }
  12036. }
  12037. this.min = min;
  12038. this.max = max;
  12039. }
  12040. getTickLimit() {
  12041. const tickOpts = this.options.ticks;
  12042. let {maxTicksLimit, stepSize} = tickOpts;
  12043. let maxTicks;
  12044. if (stepSize) {
  12045. maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
  12046. if (maxTicks > 1000) {
  12047. console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
  12048. maxTicks = 1000;
  12049. }
  12050. } else {
  12051. maxTicks = this.computeTickLimit();
  12052. maxTicksLimit = maxTicksLimit || 11;
  12053. }
  12054. if (maxTicksLimit) {
  12055. maxTicks = Math.min(maxTicksLimit, maxTicks);
  12056. }
  12057. return maxTicks;
  12058. }
  12059. computeTickLimit() {
  12060. return Number.POSITIVE_INFINITY;
  12061. }
  12062. buildTicks() {
  12063. const opts = this.options;
  12064. const tickOpts = opts.ticks;
  12065. let maxTicks = this.getTickLimit();
  12066. maxTicks = Math.max(2, maxTicks);
  12067. const numericGeneratorOptions = {
  12068. maxTicks,
  12069. bounds: opts.bounds,
  12070. min: opts.min,
  12071. max: opts.max,
  12072. precision: tickOpts.precision,
  12073. step: tickOpts.stepSize,
  12074. count: tickOpts.count,
  12075. maxDigits: this._maxDigits(),
  12076. horizontal: this.isHorizontal(),
  12077. minRotation: tickOpts.minRotation || 0,
  12078. includeBounds: tickOpts.includeBounds !== false
  12079. };
  12080. const dataRange = this._range || this;
  12081. const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
  12082. if (opts.bounds === 'ticks') {
  12083. _setMinAndMaxByKey(ticks, this, 'value');
  12084. }
  12085. if (opts.reverse) {
  12086. ticks.reverse();
  12087. this.start = this.max;
  12088. this.end = this.min;
  12089. } else {
  12090. this.start = this.min;
  12091. this.end = this.max;
  12092. }
  12093. return ticks;
  12094. }
  12095. configure() {
  12096. const ticks = this.ticks;
  12097. let start = this.min;
  12098. let end = this.max;
  12099. super.configure();
  12100. if (this.options.offset && ticks.length) {
  12101. const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
  12102. start -= offset;
  12103. end += offset;
  12104. }
  12105. this._startValue = start;
  12106. this._endValue = end;
  12107. this._valueRange = end - start;
  12108. }
  12109. getLabelForValue(value) {
  12110. return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  12111. }
  12112. }
  12113. class LinearScale extends LinearScaleBase {
  12114. determineDataLimits() {
  12115. const {min, max} = this.getMinMax(true);
  12116. this.min = isNumberFinite(min) ? min : 0;
  12117. this.max = isNumberFinite(max) ? max : 1;
  12118. this.handleTickRangeOptions();
  12119. }
  12120. computeTickLimit() {
  12121. const horizontal = this.isHorizontal();
  12122. const length = horizontal ? this.width : this.height;
  12123. const minRotation = toRadians(this.options.ticks.minRotation);
  12124. const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
  12125. const tickFont = this._resolveTickFontOptions(0);
  12126. return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
  12127. }
  12128. getPixelForValue(value) {
  12129. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  12130. }
  12131. getValueForPixel(pixel) {
  12132. return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
  12133. }
  12134. }
  12135. LinearScale.id = 'linear';
  12136. LinearScale.defaults = {
  12137. ticks: {
  12138. callback: Ticks.formatters.numeric
  12139. }
  12140. };
  12141. function isMajor(tickVal) {
  12142. const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal))));
  12143. return remain === 1;
  12144. }
  12145. function generateTicks(generationOptions, dataRange) {
  12146. const endExp = Math.floor(log10(dataRange.max));
  12147. const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
  12148. const ticks = [];
  12149. let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
  12150. let exp = Math.floor(log10(tickVal));
  12151. let significand = Math.floor(tickVal / Math.pow(10, exp));
  12152. let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
  12153. do {
  12154. ticks.push({value: tickVal, major: isMajor(tickVal)});
  12155. ++significand;
  12156. if (significand === 10) {
  12157. significand = 1;
  12158. ++exp;
  12159. precision = exp >= 0 ? 1 : precision;
  12160. }
  12161. tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
  12162. } while (exp < endExp || (exp === endExp && significand < endSignificand));
  12163. const lastTick = finiteOrDefault(generationOptions.max, tickVal);
  12164. ticks.push({value: lastTick, major: isMajor(tickVal)});
  12165. return ticks;
  12166. }
  12167. class LogarithmicScale extends Scale {
  12168. constructor(cfg) {
  12169. super(cfg);
  12170. this.start = undefined;
  12171. this.end = undefined;
  12172. this._startValue = undefined;
  12173. this._valueRange = 0;
  12174. }
  12175. parse(raw, index) {
  12176. const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);
  12177. if (value === 0) {
  12178. this._zero = true;
  12179. return undefined;
  12180. }
  12181. return isNumberFinite(value) && value > 0 ? value : null;
  12182. }
  12183. determineDataLimits() {
  12184. const {min, max} = this.getMinMax(true);
  12185. this.min = isNumberFinite(min) ? Math.max(0, min) : null;
  12186. this.max = isNumberFinite(max) ? Math.max(0, max) : null;
  12187. if (this.options.beginAtZero) {
  12188. this._zero = true;
  12189. }
  12190. this.handleTickRangeOptions();
  12191. }
  12192. handleTickRangeOptions() {
  12193. const {minDefined, maxDefined} = this.getUserBounds();
  12194. let min = this.min;
  12195. let max = this.max;
  12196. const setMin = v => (min = minDefined ? min : v);
  12197. const setMax = v => (max = maxDefined ? max : v);
  12198. const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m);
  12199. if (min === max) {
  12200. if (min <= 0) {
  12201. setMin(1);
  12202. setMax(10);
  12203. } else {
  12204. setMin(exp(min, -1));
  12205. setMax(exp(max, +1));
  12206. }
  12207. }
  12208. if (min <= 0) {
  12209. setMin(exp(max, -1));
  12210. }
  12211. if (max <= 0) {
  12212. setMax(exp(min, +1));
  12213. }
  12214. if (this._zero && this.min !== this._suggestedMin && min === exp(this.min, 0)) {
  12215. setMin(exp(min, -1));
  12216. }
  12217. this.min = min;
  12218. this.max = max;
  12219. }
  12220. buildTicks() {
  12221. const opts = this.options;
  12222. const generationOptions = {
  12223. min: this._userMin,
  12224. max: this._userMax
  12225. };
  12226. const ticks = generateTicks(generationOptions, this);
  12227. if (opts.bounds === 'ticks') {
  12228. _setMinAndMaxByKey(ticks, this, 'value');
  12229. }
  12230. if (opts.reverse) {
  12231. ticks.reverse();
  12232. this.start = this.max;
  12233. this.end = this.min;
  12234. } else {
  12235. this.start = this.min;
  12236. this.end = this.max;
  12237. }
  12238. return ticks;
  12239. }
  12240. getLabelForValue(value) {
  12241. return value === undefined
  12242. ? '0'
  12243. : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  12244. }
  12245. configure() {
  12246. const start = this.min;
  12247. super.configure();
  12248. this._startValue = log10(start);
  12249. this._valueRange = log10(this.max) - log10(start);
  12250. }
  12251. getPixelForValue(value) {
  12252. if (value === undefined || value === 0) {
  12253. value = this.min;
  12254. }
  12255. if (value === null || isNaN(value)) {
  12256. return NaN;
  12257. }
  12258. return this.getPixelForDecimal(value === this.min
  12259. ? 0
  12260. : (log10(value) - this._startValue) / this._valueRange);
  12261. }
  12262. getValueForPixel(pixel) {
  12263. const decimal = this.getDecimalForPixel(pixel);
  12264. return Math.pow(10, this._startValue + decimal * this._valueRange);
  12265. }
  12266. }
  12267. LogarithmicScale.id = 'logarithmic';
  12268. LogarithmicScale.defaults = {
  12269. ticks: {
  12270. callback: Ticks.formatters.logarithmic,
  12271. major: {
  12272. enabled: true
  12273. }
  12274. }
  12275. };
  12276. function getTickBackdropHeight(opts) {
  12277. const tickOpts = opts.ticks;
  12278. if (tickOpts.display && opts.display) {
  12279. const padding = toPadding(tickOpts.backdropPadding);
  12280. return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
  12281. }
  12282. return 0;
  12283. }
  12284. function measureLabelSize(ctx, font, label) {
  12285. label = isArray(label) ? label : [label];
  12286. return {
  12287. w: _longestText(ctx, font.string, label),
  12288. h: label.length * font.lineHeight
  12289. };
  12290. }
  12291. function determineLimits(angle, pos, size, min, max) {
  12292. if (angle === min || angle === max) {
  12293. return {
  12294. start: pos - (size / 2),
  12295. end: pos + (size / 2)
  12296. };
  12297. } else if (angle < min || angle > max) {
  12298. return {
  12299. start: pos - size,
  12300. end: pos
  12301. };
  12302. }
  12303. return {
  12304. start: pos,
  12305. end: pos + size
  12306. };
  12307. }
  12308. function fitWithPointLabels(scale) {
  12309. const orig = {
  12310. l: scale.left + scale._padding.left,
  12311. r: scale.right - scale._padding.right,
  12312. t: scale.top + scale._padding.top,
  12313. b: scale.bottom - scale._padding.bottom
  12314. };
  12315. const limits = Object.assign({}, orig);
  12316. const labelSizes = [];
  12317. const padding = [];
  12318. const valueCount = scale._pointLabels.length;
  12319. const pointLabelOpts = scale.options.pointLabels;
  12320. const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
  12321. for (let i = 0; i < valueCount; i++) {
  12322. const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
  12323. padding[i] = opts.padding;
  12324. const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
  12325. const plFont = toFont(opts.font);
  12326. const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
  12327. labelSizes[i] = textSize;
  12328. const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
  12329. const angle = Math.round(toDegrees(angleRadians));
  12330. const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  12331. const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  12332. updateLimits(limits, orig, angleRadians, hLimits, vLimits);
  12333. }
  12334. scale.setCenterPoint(
  12335. orig.l - limits.l,
  12336. limits.r - orig.r,
  12337. orig.t - limits.t,
  12338. limits.b - orig.b
  12339. );
  12340. scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  12341. }
  12342. function updateLimits(limits, orig, angle, hLimits, vLimits) {
  12343. const sin = Math.abs(Math.sin(angle));
  12344. const cos = Math.abs(Math.cos(angle));
  12345. let x = 0;
  12346. let y = 0;
  12347. if (hLimits.start < orig.l) {
  12348. x = (orig.l - hLimits.start) / sin;
  12349. limits.l = Math.min(limits.l, orig.l - x);
  12350. } else if (hLimits.end > orig.r) {
  12351. x = (hLimits.end - orig.r) / sin;
  12352. limits.r = Math.max(limits.r, orig.r + x);
  12353. }
  12354. if (vLimits.start < orig.t) {
  12355. y = (orig.t - vLimits.start) / cos;
  12356. limits.t = Math.min(limits.t, orig.t - y);
  12357. } else if (vLimits.end > orig.b) {
  12358. y = (vLimits.end - orig.b) / cos;
  12359. limits.b = Math.max(limits.b, orig.b + y);
  12360. }
  12361. }
  12362. function buildPointLabelItems(scale, labelSizes, padding) {
  12363. const items = [];
  12364. const valueCount = scale._pointLabels.length;
  12365. const opts = scale.options;
  12366. const extra = getTickBackdropHeight(opts) / 2;
  12367. const outerDistance = scale.drawingArea;
  12368. const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0;
  12369. for (let i = 0; i < valueCount; i++) {
  12370. const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle);
  12371. const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
  12372. const size = labelSizes[i];
  12373. const y = yForAngle(pointLabelPosition.y, size.h, angle);
  12374. const textAlign = getTextAlignForAngle(angle);
  12375. const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
  12376. items.push({
  12377. x: pointLabelPosition.x,
  12378. y,
  12379. textAlign,
  12380. left,
  12381. top: y,
  12382. right: left + size.w,
  12383. bottom: y + size.h
  12384. });
  12385. }
  12386. return items;
  12387. }
  12388. function getTextAlignForAngle(angle) {
  12389. if (angle === 0 || angle === 180) {
  12390. return 'center';
  12391. } else if (angle < 180) {
  12392. return 'left';
  12393. }
  12394. return 'right';
  12395. }
  12396. function leftForTextAlign(x, w, align) {
  12397. if (align === 'right') {
  12398. x -= w;
  12399. } else if (align === 'center') {
  12400. x -= (w / 2);
  12401. }
  12402. return x;
  12403. }
  12404. function yForAngle(y, h, angle) {
  12405. if (angle === 90 || angle === 270) {
  12406. y -= (h / 2);
  12407. } else if (angle > 270 || angle < 90) {
  12408. y -= h;
  12409. }
  12410. return y;
  12411. }
  12412. function drawPointLabels(scale, labelCount) {
  12413. const {ctx, options: {pointLabels}} = scale;
  12414. for (let i = labelCount - 1; i >= 0; i--) {
  12415. const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
  12416. const plFont = toFont(optsAtIndex.font);
  12417. const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i];
  12418. const {backdropColor} = optsAtIndex;
  12419. if (!isNullOrUndef(backdropColor)) {
  12420. const padding = toPadding(optsAtIndex.backdropPadding);
  12421. ctx.fillStyle = backdropColor;
  12422. ctx.fillRect(left - padding.left, top - padding.top, right - left + padding.width, bottom - top + padding.height);
  12423. }
  12424. renderText(
  12425. ctx,
  12426. scale._pointLabels[i],
  12427. x,
  12428. y + (plFont.lineHeight / 2),
  12429. plFont,
  12430. {
  12431. color: optsAtIndex.color,
  12432. textAlign: textAlign,
  12433. textBaseline: 'middle'
  12434. }
  12435. );
  12436. }
  12437. }
  12438. function pathRadiusLine(scale, radius, circular, labelCount) {
  12439. const {ctx} = scale;
  12440. if (circular) {
  12441. ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
  12442. } else {
  12443. let pointPosition = scale.getPointPosition(0, radius);
  12444. ctx.moveTo(pointPosition.x, pointPosition.y);
  12445. for (let i = 1; i < labelCount; i++) {
  12446. pointPosition = scale.getPointPosition(i, radius);
  12447. ctx.lineTo(pointPosition.x, pointPosition.y);
  12448. }
  12449. }
  12450. }
  12451. function drawRadiusLine(scale, gridLineOpts, radius, labelCount) {
  12452. const ctx = scale.ctx;
  12453. const circular = gridLineOpts.circular;
  12454. const {color, lineWidth} = gridLineOpts;
  12455. if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) {
  12456. return;
  12457. }
  12458. ctx.save();
  12459. ctx.strokeStyle = color;
  12460. ctx.lineWidth = lineWidth;
  12461. ctx.setLineDash(gridLineOpts.borderDash);
  12462. ctx.lineDashOffset = gridLineOpts.borderDashOffset;
  12463. ctx.beginPath();
  12464. pathRadiusLine(scale, radius, circular, labelCount);
  12465. ctx.closePath();
  12466. ctx.stroke();
  12467. ctx.restore();
  12468. }
  12469. function createPointLabelContext(parent, index, label) {
  12470. return createContext(parent, {
  12471. label,
  12472. index,
  12473. type: 'pointLabel'
  12474. });
  12475. }
  12476. class RadialLinearScale extends LinearScaleBase {
  12477. constructor(cfg) {
  12478. super(cfg);
  12479. this.xCenter = undefined;
  12480. this.yCenter = undefined;
  12481. this.drawingArea = undefined;
  12482. this._pointLabels = [];
  12483. this._pointLabelItems = [];
  12484. }
  12485. setDimensions() {
  12486. const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
  12487. const w = this.width = this.maxWidth - padding.width;
  12488. const h = this.height = this.maxHeight - padding.height;
  12489. this.xCenter = Math.floor(this.left + w / 2 + padding.left);
  12490. this.yCenter = Math.floor(this.top + h / 2 + padding.top);
  12491. this.drawingArea = Math.floor(Math.min(w, h) / 2);
  12492. }
  12493. determineDataLimits() {
  12494. const {min, max} = this.getMinMax(false);
  12495. this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
  12496. this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
  12497. this.handleTickRangeOptions();
  12498. }
  12499. computeTickLimit() {
  12500. return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  12501. }
  12502. generateTickLabels(ticks) {
  12503. LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
  12504. this._pointLabels = this.getLabels()
  12505. .map((value, index) => {
  12506. const label = callback(this.options.pointLabels.callback, [value, index], this);
  12507. return label || label === 0 ? label : '';
  12508. })
  12509. .filter((v, i) => this.chart.getDataVisibility(i));
  12510. }
  12511. fit() {
  12512. const opts = this.options;
  12513. if (opts.display && opts.pointLabels.display) {
  12514. fitWithPointLabels(this);
  12515. } else {
  12516. this.setCenterPoint(0, 0, 0, 0);
  12517. }
  12518. }
  12519. setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
  12520. this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
  12521. this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
  12522. this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
  12523. }
  12524. getIndexAngle(index) {
  12525. const angleMultiplier = TAU / (this._pointLabels.length || 1);
  12526. const startAngle = this.options.startAngle || 0;
  12527. return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
  12528. }
  12529. getDistanceFromCenterForValue(value) {
  12530. if (isNullOrUndef(value)) {
  12531. return NaN;
  12532. }
  12533. const scalingFactor = this.drawingArea / (this.max - this.min);
  12534. if (this.options.reverse) {
  12535. return (this.max - value) * scalingFactor;
  12536. }
  12537. return (value - this.min) * scalingFactor;
  12538. }
  12539. getValueForDistanceFromCenter(distance) {
  12540. if (isNullOrUndef(distance)) {
  12541. return NaN;
  12542. }
  12543. const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
  12544. return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
  12545. }
  12546. getPointLabelContext(index) {
  12547. const pointLabels = this._pointLabels || [];
  12548. if (index >= 0 && index < pointLabels.length) {
  12549. const pointLabel = pointLabels[index];
  12550. return createPointLabelContext(this.getContext(), index, pointLabel);
  12551. }
  12552. }
  12553. getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
  12554. const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
  12555. return {
  12556. x: Math.cos(angle) * distanceFromCenter + this.xCenter,
  12557. y: Math.sin(angle) * distanceFromCenter + this.yCenter,
  12558. angle
  12559. };
  12560. }
  12561. getPointPositionForValue(index, value) {
  12562. return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  12563. }
  12564. getBasePosition(index) {
  12565. return this.getPointPositionForValue(index || 0, this.getBaseValue());
  12566. }
  12567. getPointLabelPosition(index) {
  12568. const {left, top, right, bottom} = this._pointLabelItems[index];
  12569. return {
  12570. left,
  12571. top,
  12572. right,
  12573. bottom,
  12574. };
  12575. }
  12576. drawBackground() {
  12577. const {backgroundColor, grid: {circular}} = this.options;
  12578. if (backgroundColor) {
  12579. const ctx = this.ctx;
  12580. ctx.save();
  12581. ctx.beginPath();
  12582. pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
  12583. ctx.closePath();
  12584. ctx.fillStyle = backgroundColor;
  12585. ctx.fill();
  12586. ctx.restore();
  12587. }
  12588. }
  12589. drawGrid() {
  12590. const ctx = this.ctx;
  12591. const opts = this.options;
  12592. const {angleLines, grid} = opts;
  12593. const labelCount = this._pointLabels.length;
  12594. let i, offset, position;
  12595. if (opts.pointLabels.display) {
  12596. drawPointLabels(this, labelCount);
  12597. }
  12598. if (grid.display) {
  12599. this.ticks.forEach((tick, index) => {
  12600. if (index !== 0) {
  12601. offset = this.getDistanceFromCenterForValue(tick.value);
  12602. const optsAtIndex = grid.setContext(this.getContext(index - 1));
  12603. drawRadiusLine(this, optsAtIndex, offset, labelCount);
  12604. }
  12605. });
  12606. }
  12607. if (angleLines.display) {
  12608. ctx.save();
  12609. for (i = labelCount - 1; i >= 0; i--) {
  12610. const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
  12611. const {color, lineWidth} = optsAtIndex;
  12612. if (!lineWidth || !color) {
  12613. continue;
  12614. }
  12615. ctx.lineWidth = lineWidth;
  12616. ctx.strokeStyle = color;
  12617. ctx.setLineDash(optsAtIndex.borderDash);
  12618. ctx.lineDashOffset = optsAtIndex.borderDashOffset;
  12619. offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
  12620. position = this.getPointPosition(i, offset);
  12621. ctx.beginPath();
  12622. ctx.moveTo(this.xCenter, this.yCenter);
  12623. ctx.lineTo(position.x, position.y);
  12624. ctx.stroke();
  12625. }
  12626. ctx.restore();
  12627. }
  12628. }
  12629. drawBorder() {}
  12630. drawLabels() {
  12631. const ctx = this.ctx;
  12632. const opts = this.options;
  12633. const tickOpts = opts.ticks;
  12634. if (!tickOpts.display) {
  12635. return;
  12636. }
  12637. const startAngle = this.getIndexAngle(0);
  12638. let offset, width;
  12639. ctx.save();
  12640. ctx.translate(this.xCenter, this.yCenter);
  12641. ctx.rotate(startAngle);
  12642. ctx.textAlign = 'center';
  12643. ctx.textBaseline = 'middle';
  12644. this.ticks.forEach((tick, index) => {
  12645. if (index === 0 && !opts.reverse) {
  12646. return;
  12647. }
  12648. const optsAtIndex = tickOpts.setContext(this.getContext(index));
  12649. const tickFont = toFont(optsAtIndex.font);
  12650. offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
  12651. if (optsAtIndex.showLabelBackdrop) {
  12652. ctx.font = tickFont.string;
  12653. width = ctx.measureText(tick.label).width;
  12654. ctx.fillStyle = optsAtIndex.backdropColor;
  12655. const padding = toPadding(optsAtIndex.backdropPadding);
  12656. ctx.fillRect(
  12657. -width / 2 - padding.left,
  12658. -offset - tickFont.size / 2 - padding.top,
  12659. width + padding.width,
  12660. tickFont.size + padding.height
  12661. );
  12662. }
  12663. renderText(ctx, tick.label, 0, -offset, tickFont, {
  12664. color: optsAtIndex.color,
  12665. });
  12666. });
  12667. ctx.restore();
  12668. }
  12669. drawTitle() {}
  12670. }
  12671. RadialLinearScale.id = 'radialLinear';
  12672. RadialLinearScale.defaults = {
  12673. display: true,
  12674. animate: true,
  12675. position: 'chartArea',
  12676. angleLines: {
  12677. display: true,
  12678. lineWidth: 1,
  12679. borderDash: [],
  12680. borderDashOffset: 0.0
  12681. },
  12682. grid: {
  12683. circular: false
  12684. },
  12685. startAngle: 0,
  12686. ticks: {
  12687. showLabelBackdrop: true,
  12688. callback: Ticks.formatters.numeric
  12689. },
  12690. pointLabels: {
  12691. backdropColor: undefined,
  12692. backdropPadding: 2,
  12693. display: true,
  12694. font: {
  12695. size: 10
  12696. },
  12697. callback(label) {
  12698. return label;
  12699. },
  12700. padding: 5,
  12701. centerPointLabels: false
  12702. }
  12703. };
  12704. RadialLinearScale.defaultRoutes = {
  12705. 'angleLines.color': 'borderColor',
  12706. 'pointLabels.color': 'color',
  12707. 'ticks.color': 'color'
  12708. };
  12709. RadialLinearScale.descriptors = {
  12710. angleLines: {
  12711. _fallback: 'grid'
  12712. }
  12713. };
  12714. const INTERVALS = {
  12715. millisecond: {common: true, size: 1, steps: 1000},
  12716. second: {common: true, size: 1000, steps: 60},
  12717. minute: {common: true, size: 60000, steps: 60},
  12718. hour: {common: true, size: 3600000, steps: 24},
  12719. day: {common: true, size: 86400000, steps: 30},
  12720. week: {common: false, size: 604800000, steps: 4},
  12721. month: {common: true, size: 2.628e9, steps: 12},
  12722. quarter: {common: false, size: 7.884e9, steps: 4},
  12723. year: {common: true, size: 3.154e10}
  12724. };
  12725. const UNITS = (Object.keys(INTERVALS));
  12726. function sorter(a, b) {
  12727. return a - b;
  12728. }
  12729. function parse(scale, input) {
  12730. if (isNullOrUndef(input)) {
  12731. return null;
  12732. }
  12733. const adapter = scale._adapter;
  12734. const {parser, round, isoWeekday} = scale._parseOpts;
  12735. let value = input;
  12736. if (typeof parser === 'function') {
  12737. value = parser(value);
  12738. }
  12739. if (!isNumberFinite(value)) {
  12740. value = typeof parser === 'string'
  12741. ? adapter.parse(value, parser)
  12742. : adapter.parse(value);
  12743. }
  12744. if (value === null) {
  12745. return null;
  12746. }
  12747. if (round) {
  12748. value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true)
  12749. ? adapter.startOf(value, 'isoWeek', isoWeekday)
  12750. : adapter.startOf(value, round);
  12751. }
  12752. return +value;
  12753. }
  12754. function determineUnitForAutoTicks(minUnit, min, max, capacity) {
  12755. const ilen = UNITS.length;
  12756. for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
  12757. const interval = INTERVALS[UNITS[i]];
  12758. const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
  12759. if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
  12760. return UNITS[i];
  12761. }
  12762. }
  12763. return UNITS[ilen - 1];
  12764. }
  12765. function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
  12766. for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
  12767. const unit = UNITS[i];
  12768. if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
  12769. return unit;
  12770. }
  12771. }
  12772. return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  12773. }
  12774. function determineMajorUnit(unit) {
  12775. for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
  12776. if (INTERVALS[UNITS[i]].common) {
  12777. return UNITS[i];
  12778. }
  12779. }
  12780. }
  12781. function addTick(ticks, time, timestamps) {
  12782. if (!timestamps) {
  12783. ticks[time] = true;
  12784. } else if (timestamps.length) {
  12785. const {lo, hi} = _lookup(timestamps, time);
  12786. const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
  12787. ticks[timestamp] = true;
  12788. }
  12789. }
  12790. function setMajorTicks(scale, ticks, map, majorUnit) {
  12791. const adapter = scale._adapter;
  12792. const first = +adapter.startOf(ticks[0].value, majorUnit);
  12793. const last = ticks[ticks.length - 1].value;
  12794. let major, index;
  12795. for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
  12796. index = map[major];
  12797. if (index >= 0) {
  12798. ticks[index].major = true;
  12799. }
  12800. }
  12801. return ticks;
  12802. }
  12803. function ticksFromTimestamps(scale, values, majorUnit) {
  12804. const ticks = [];
  12805. const map = {};
  12806. const ilen = values.length;
  12807. let i, value;
  12808. for (i = 0; i < ilen; ++i) {
  12809. value = values[i];
  12810. map[value] = i;
  12811. ticks.push({
  12812. value,
  12813. major: false
  12814. });
  12815. }
  12816. return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  12817. }
  12818. class TimeScale extends Scale {
  12819. constructor(props) {
  12820. super(props);
  12821. this._cache = {
  12822. data: [],
  12823. labels: [],
  12824. all: []
  12825. };
  12826. this._unit = 'day';
  12827. this._majorUnit = undefined;
  12828. this._offsets = {};
  12829. this._normalized = false;
  12830. this._parseOpts = undefined;
  12831. }
  12832. init(scaleOpts, opts) {
  12833. const time = scaleOpts.time || (scaleOpts.time = {});
  12834. const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date);
  12835. mergeIf(time.displayFormats, adapter.formats());
  12836. this._parseOpts = {
  12837. parser: time.parser,
  12838. round: time.round,
  12839. isoWeekday: time.isoWeekday
  12840. };
  12841. super.init(scaleOpts);
  12842. this._normalized = opts.normalized;
  12843. }
  12844. parse(raw, index) {
  12845. if (raw === undefined) {
  12846. return null;
  12847. }
  12848. return parse(this, raw);
  12849. }
  12850. beforeLayout() {
  12851. super.beforeLayout();
  12852. this._cache = {
  12853. data: [],
  12854. labels: [],
  12855. all: []
  12856. };
  12857. }
  12858. determineDataLimits() {
  12859. const options = this.options;
  12860. const adapter = this._adapter;
  12861. const unit = options.time.unit || 'day';
  12862. let {min, max, minDefined, maxDefined} = this.getUserBounds();
  12863. function _applyBounds(bounds) {
  12864. if (!minDefined && !isNaN(bounds.min)) {
  12865. min = Math.min(min, bounds.min);
  12866. }
  12867. if (!maxDefined && !isNaN(bounds.max)) {
  12868. max = Math.max(max, bounds.max);
  12869. }
  12870. }
  12871. if (!minDefined || !maxDefined) {
  12872. _applyBounds(this._getLabelBounds());
  12873. if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
  12874. _applyBounds(this.getMinMax(false));
  12875. }
  12876. }
  12877. min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
  12878. max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
  12879. this.min = Math.min(min, max - 1);
  12880. this.max = Math.max(min + 1, max);
  12881. }
  12882. _getLabelBounds() {
  12883. const arr = this.getLabelTimestamps();
  12884. let min = Number.POSITIVE_INFINITY;
  12885. let max = Number.NEGATIVE_INFINITY;
  12886. if (arr.length) {
  12887. min = arr[0];
  12888. max = arr[arr.length - 1];
  12889. }
  12890. return {min, max};
  12891. }
  12892. buildTicks() {
  12893. const options = this.options;
  12894. const timeOpts = options.time;
  12895. const tickOpts = options.ticks;
  12896. const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
  12897. if (options.bounds === 'ticks' && timestamps.length) {
  12898. this.min = this._userMin || timestamps[0];
  12899. this.max = this._userMax || timestamps[timestamps.length - 1];
  12900. }
  12901. const min = this.min;
  12902. const max = this.max;
  12903. const ticks = _filterBetween(timestamps, min, max);
  12904. this._unit = timeOpts.unit || (tickOpts.autoSkip
  12905. ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min))
  12906. : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
  12907. this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined
  12908. : determineMajorUnit(this._unit);
  12909. this.initOffsets(timestamps);
  12910. if (options.reverse) {
  12911. ticks.reverse();
  12912. }
  12913. return ticksFromTimestamps(this, ticks, this._majorUnit);
  12914. }
  12915. initOffsets(timestamps) {
  12916. let start = 0;
  12917. let end = 0;
  12918. let first, last;
  12919. if (this.options.offset && timestamps.length) {
  12920. first = this.getDecimalForValue(timestamps[0]);
  12921. if (timestamps.length === 1) {
  12922. start = 1 - first;
  12923. } else {
  12924. start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
  12925. }
  12926. last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
  12927. if (timestamps.length === 1) {
  12928. end = last;
  12929. } else {
  12930. end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
  12931. }
  12932. }
  12933. const limit = timestamps.length < 3 ? 0.5 : 0.25;
  12934. start = _limitValue(start, 0, limit);
  12935. end = _limitValue(end, 0, limit);
  12936. this._offsets = {start, end, factor: 1 / (start + 1 + end)};
  12937. }
  12938. _generate() {
  12939. const adapter = this._adapter;
  12940. const min = this.min;
  12941. const max = this.max;
  12942. const options = this.options;
  12943. const timeOpts = options.time;
  12944. const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
  12945. const stepSize = valueOrDefault(timeOpts.stepSize, 1);
  12946. const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
  12947. const hasWeekday = isNumber(weekday) || weekday === true;
  12948. const ticks = {};
  12949. let first = min;
  12950. let time, count;
  12951. if (hasWeekday) {
  12952. first = +adapter.startOf(first, 'isoWeek', weekday);
  12953. }
  12954. first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
  12955. if (adapter.diff(max, min, minor) > 100000 * stepSize) {
  12956. throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
  12957. }
  12958. const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
  12959. for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {
  12960. addTick(ticks, time, timestamps);
  12961. }
  12962. if (time === max || options.bounds === 'ticks' || count === 1) {
  12963. addTick(ticks, time, timestamps);
  12964. }
  12965. return Object.keys(ticks).sort((a, b) => a - b).map(x => +x);
  12966. }
  12967. getLabelForValue(value) {
  12968. const adapter = this._adapter;
  12969. const timeOpts = this.options.time;
  12970. if (timeOpts.tooltipFormat) {
  12971. return adapter.format(value, timeOpts.tooltipFormat);
  12972. }
  12973. return adapter.format(value, timeOpts.displayFormats.datetime);
  12974. }
  12975. _tickFormatFunction(time, index, ticks, format) {
  12976. const options = this.options;
  12977. const formats = options.time.displayFormats;
  12978. const unit = this._unit;
  12979. const majorUnit = this._majorUnit;
  12980. const minorFormat = unit && formats[unit];
  12981. const majorFormat = majorUnit && formats[majorUnit];
  12982. const tick = ticks[index];
  12983. const major = majorUnit && majorFormat && tick && tick.major;
  12984. const label = this._adapter.format(time, format || (major ? majorFormat : minorFormat));
  12985. const formatter = options.ticks.callback;
  12986. return formatter ? callback(formatter, [label, index, ticks], this) : label;
  12987. }
  12988. generateTickLabels(ticks) {
  12989. let i, ilen, tick;
  12990. for (i = 0, ilen = ticks.length; i < ilen; ++i) {
  12991. tick = ticks[i];
  12992. tick.label = this._tickFormatFunction(tick.value, i, ticks);
  12993. }
  12994. }
  12995. getDecimalForValue(value) {
  12996. return value === null ? NaN : (value - this.min) / (this.max - this.min);
  12997. }
  12998. getPixelForValue(value) {
  12999. const offsets = this._offsets;
  13000. const pos = this.getDecimalForValue(value);
  13001. return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
  13002. }
  13003. getValueForPixel(pixel) {
  13004. const offsets = this._offsets;
  13005. const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  13006. return this.min + pos * (this.max - this.min);
  13007. }
  13008. _getLabelSize(label) {
  13009. const ticksOpts = this.options.ticks;
  13010. const tickLabelWidth = this.ctx.measureText(label).width;
  13011. const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
  13012. const cosRotation = Math.cos(angle);
  13013. const sinRotation = Math.sin(angle);
  13014. const tickFontSize = this._resolveTickFontOptions(0).size;
  13015. return {
  13016. w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
  13017. h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
  13018. };
  13019. }
  13020. _getLabelCapacity(exampleTime) {
  13021. const timeOpts = this.options.time;
  13022. const displayFormats = timeOpts.displayFormats;
  13023. const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
  13024. const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format);
  13025. const size = this._getLabelSize(exampleLabel);
  13026. const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
  13027. return capacity > 0 ? capacity : 1;
  13028. }
  13029. getDataTimestamps() {
  13030. let timestamps = this._cache.data || [];
  13031. let i, ilen;
  13032. if (timestamps.length) {
  13033. return timestamps;
  13034. }
  13035. const metas = this.getMatchingVisibleMetas();
  13036. if (this._normalized && metas.length) {
  13037. return (this._cache.data = metas[0].controller.getAllParsedValues(this));
  13038. }
  13039. for (i = 0, ilen = metas.length; i < ilen; ++i) {
  13040. timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
  13041. }
  13042. return (this._cache.data = this.normalize(timestamps));
  13043. }
  13044. getLabelTimestamps() {
  13045. const timestamps = this._cache.labels || [];
  13046. let i, ilen;
  13047. if (timestamps.length) {
  13048. return timestamps;
  13049. }
  13050. const labels = this.getLabels();
  13051. for (i = 0, ilen = labels.length; i < ilen; ++i) {
  13052. timestamps.push(parse(this, labels[i]));
  13053. }
  13054. return (this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps));
  13055. }
  13056. normalize(values) {
  13057. return _arrayUnique(values.sort(sorter));
  13058. }
  13059. }
  13060. TimeScale.id = 'time';
  13061. TimeScale.defaults = {
  13062. bounds: 'data',
  13063. adapters: {},
  13064. time: {
  13065. parser: false,
  13066. unit: false,
  13067. round: false,
  13068. isoWeekday: false,
  13069. minUnit: 'millisecond',
  13070. displayFormats: {}
  13071. },
  13072. ticks: {
  13073. source: 'auto',
  13074. major: {
  13075. enabled: false
  13076. }
  13077. }
  13078. };
  13079. function interpolate(table, val, reverse) {
  13080. let lo = 0;
  13081. let hi = table.length - 1;
  13082. let prevSource, nextSource, prevTarget, nextTarget;
  13083. if (reverse) {
  13084. if (val >= table[lo].pos && val <= table[hi].pos) {
  13085. ({lo, hi} = _lookupByKey(table, 'pos', val));
  13086. }
  13087. ({pos: prevSource, time: prevTarget} = table[lo]);
  13088. ({pos: nextSource, time: nextTarget} = table[hi]);
  13089. } else {
  13090. if (val >= table[lo].time && val <= table[hi].time) {
  13091. ({lo, hi} = _lookupByKey(table, 'time', val));
  13092. }
  13093. ({time: prevSource, pos: prevTarget} = table[lo]);
  13094. ({time: nextSource, pos: nextTarget} = table[hi]);
  13095. }
  13096. const span = nextSource - prevSource;
  13097. return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  13098. }
  13099. class TimeSeriesScale extends TimeScale {
  13100. constructor(props) {
  13101. super(props);
  13102. this._table = [];
  13103. this._minPos = undefined;
  13104. this._tableRange = undefined;
  13105. }
  13106. initOffsets() {
  13107. const timestamps = this._getTimestampsForTable();
  13108. const table = this._table = this.buildLookupTable(timestamps);
  13109. this._minPos = interpolate(table, this.min);
  13110. this._tableRange = interpolate(table, this.max) - this._minPos;
  13111. super.initOffsets(timestamps);
  13112. }
  13113. buildLookupTable(timestamps) {
  13114. const {min, max} = this;
  13115. const items = [];
  13116. const table = [];
  13117. let i, ilen, prev, curr, next;
  13118. for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
  13119. curr = timestamps[i];
  13120. if (curr >= min && curr <= max) {
  13121. items.push(curr);
  13122. }
  13123. }
  13124. if (items.length < 2) {
  13125. return [
  13126. {time: min, pos: 0},
  13127. {time: max, pos: 1}
  13128. ];
  13129. }
  13130. for (i = 0, ilen = items.length; i < ilen; ++i) {
  13131. next = items[i + 1];
  13132. prev = items[i - 1];
  13133. curr = items[i];
  13134. if (Math.round((next + prev) / 2) !== curr) {
  13135. table.push({time: curr, pos: i / (ilen - 1)});
  13136. }
  13137. }
  13138. return table;
  13139. }
  13140. _getTimestampsForTable() {
  13141. let timestamps = this._cache.all || [];
  13142. if (timestamps.length) {
  13143. return timestamps;
  13144. }
  13145. const data = this.getDataTimestamps();
  13146. const label = this.getLabelTimestamps();
  13147. if (data.length && label.length) {
  13148. timestamps = this.normalize(data.concat(label));
  13149. } else {
  13150. timestamps = data.length ? data : label;
  13151. }
  13152. timestamps = this._cache.all = timestamps;
  13153. return timestamps;
  13154. }
  13155. getDecimalForValue(value) {
  13156. return (interpolate(this._table, value) - this._minPos) / this._tableRange;
  13157. }
  13158. getValueForPixel(pixel) {
  13159. const offsets = this._offsets;
  13160. const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  13161. return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
  13162. }
  13163. }
  13164. TimeSeriesScale.id = 'timeseries';
  13165. TimeSeriesScale.defaults = TimeScale.defaults;
  13166. var scales = /*#__PURE__*/Object.freeze({
  13167. __proto__: null,
  13168. CategoryScale: CategoryScale,
  13169. LinearScale: LinearScale,
  13170. LogarithmicScale: LogarithmicScale,
  13171. RadialLinearScale: RadialLinearScale,
  13172. TimeScale: TimeScale,
  13173. TimeSeriesScale: TimeSeriesScale
  13174. });
  13175. Chart.register(controllers, scales, elements, plugins);
  13176. Chart.helpers = {...helpers};
  13177. Chart._adapters = _adapters;
  13178. Chart.Animation = Animation;
  13179. Chart.Animations = Animations;
  13180. Chart.animator = animator;
  13181. Chart.controllers = registry.controllers.items;
  13182. Chart.DatasetController = DatasetController;
  13183. Chart.Element = Element;
  13184. Chart.elements = elements;
  13185. Chart.Interaction = Interaction;
  13186. Chart.layouts = layouts;
  13187. Chart.platforms = platforms;
  13188. Chart.Scale = Scale;
  13189. Chart.Ticks = Ticks;
  13190. Object.assign(Chart, controllers, scales, elements, plugins, platforms);
  13191. Chart.Chart = Chart;
  13192. if (typeof window !== 'undefined') {
  13193. window.Chart = Chart;
  13194. }
  13195. return Chart;
  13196. }));