Açıklama Yok

chart.js 392KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449
  1. /*!
  2. * Chart.js v4.4.0
  3. * https://www.chartjs.org
  4. * (c) 2023 Chart.js Contributors
  5. * Released under the MIT License
  6. */
  7. import { r as requestAnimFrame, a as resolve, e as effects, c as color, i as isObject, d as defaults, b as isArray, v as valueOrDefault, u as unlistenArrayEvents, l as listenArrayEvents, f as resolveObjectKey, g as isNumberFinite, h as defined, s as sign, j as createContext, k as isNullOrUndef, _ as _arrayUnique, t as toRadians, m as toPercentage, n as toDimension, T as TAU, o as formatNumber, p as _angleBetween, H as HALF_PI, P as PI, q as _getStartAndCountOfVisiblePoints, w as _scaleRangesChanged, x as isNumber, y as _parseObjectDataRadialScale, z as getRelativePosition, A as _rlookupByKey, B as _lookupByKey, C as _isPointInArea, D as getAngleFromPoint, E as toPadding, F as each, G as getMaximumSize, I as _getParentNode, J as readUsedSize, K as supportsEventListenerOptions, L as throttled, M as _isDomSupported, N as _factorize, O as finiteOrDefault, Q as callback, R as _addGrace, S as _limitValue, U as toDegrees, V as _measureText, W as _int16Range, X as _alignPixel, Y as clipArea, Z as renderText, $ as unclipArea, a0 as toFont, a1 as _toLeftRightCenter, a2 as _alignStartEnd, a3 as overrides, a4 as merge, a5 as _capitalize, a6 as descriptors, a7 as isFunction, a8 as _attachContext, a9 as _createResolver, aa as _descriptors, ab as mergeIf, ac as uid, ad as debounce, ae as retinaScale, af as clearCanvas, ag as setsEqual, ah as _elementsEqual, ai as _isClickEvent, aj as _isBetween, ak as _readValueToProps, al as _updateBezierControlPoints, am as _computeSegments, an as _boundSegments, ao as _steppedInterpolation, ap as _bezierInterpolation, aq as _pointInLine, ar as _steppedLineTo, as as _bezierCurveTo, at as drawPoint, au as addRoundedRectPath, av as toTRBL, aw as toTRBLCorners, ax as _boundSegment, ay as _normalizeAngle, az as getRtlAdapter, aA as overrideTextDirection, aB as _textX, aC as restoreTextDirection, aD as drawPointLegend, aE as distanceBetweenPoints, aF as noop, aG as _setMinAndMaxByKey, aH as niceNum, aI as almostWhole, aJ as almostEquals, aK as _decimalPlaces, aL as Ticks, aM as log10, aN as _longestText, aO as _filterBetween, aP as _lookup } from './chunks/helpers.segment.js';
  8. import '@kurkle/color';
  9. class Animator {
  10. constructor(){
  11. this._request = null;
  12. this._charts = new Map();
  13. this._running = false;
  14. this._lastDate = undefined;
  15. }
  16. _notify(chart, anims, date, type) {
  17. const callbacks = anims.listeners[type];
  18. const numSteps = anims.duration;
  19. callbacks.forEach((fn)=>fn({
  20. chart,
  21. initial: anims.initial,
  22. numSteps,
  23. currentStep: Math.min(date - anims.start, numSteps)
  24. }));
  25. }
  26. _refresh() {
  27. if (this._request) {
  28. return;
  29. }
  30. this._running = true;
  31. this._request = requestAnimFrame.call(window, ()=>{
  32. this._update();
  33. this._request = null;
  34. if (this._running) {
  35. this._refresh();
  36. }
  37. });
  38. }
  39. _update(date = Date.now()) {
  40. let remaining = 0;
  41. this._charts.forEach((anims, chart)=>{
  42. if (!anims.running || !anims.items.length) {
  43. return;
  44. }
  45. const items = anims.items;
  46. let i = items.length - 1;
  47. let draw = false;
  48. let item;
  49. for(; i >= 0; --i){
  50. item = items[i];
  51. if (item._active) {
  52. if (item._total > anims.duration) {
  53. anims.duration = item._total;
  54. }
  55. item.tick(date);
  56. draw = true;
  57. } else {
  58. items[i] = items[items.length - 1];
  59. items.pop();
  60. }
  61. }
  62. if (draw) {
  63. chart.draw();
  64. this._notify(chart, anims, date, 'progress');
  65. }
  66. if (!items.length) {
  67. anims.running = false;
  68. this._notify(chart, anims, date, 'complete');
  69. anims.initial = false;
  70. }
  71. remaining += items.length;
  72. });
  73. this._lastDate = date;
  74. if (remaining === 0) {
  75. this._running = false;
  76. }
  77. }
  78. _getAnims(chart) {
  79. const charts = this._charts;
  80. let anims = charts.get(chart);
  81. if (!anims) {
  82. anims = {
  83. running: false,
  84. initial: true,
  85. items: [],
  86. listeners: {
  87. complete: [],
  88. progress: []
  89. }
  90. };
  91. charts.set(chart, anims);
  92. }
  93. return anims;
  94. }
  95. listen(chart, event, cb) {
  96. this._getAnims(chart).listeners[event].push(cb);
  97. }
  98. add(chart, items) {
  99. if (!items || !items.length) {
  100. return;
  101. }
  102. this._getAnims(chart).items.push(...items);
  103. }
  104. has(chart) {
  105. return this._getAnims(chart).items.length > 0;
  106. }
  107. start(chart) {
  108. const anims = this._charts.get(chart);
  109. if (!anims) {
  110. return;
  111. }
  112. anims.running = true;
  113. anims.start = Date.now();
  114. anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
  115. this._refresh();
  116. }
  117. running(chart) {
  118. if (!this._running) {
  119. return false;
  120. }
  121. const anims = this._charts.get(chart);
  122. if (!anims || !anims.running || !anims.items.length) {
  123. return false;
  124. }
  125. return true;
  126. }
  127. stop(chart) {
  128. const anims = this._charts.get(chart);
  129. if (!anims || !anims.items.length) {
  130. return;
  131. }
  132. const items = anims.items;
  133. let i = items.length - 1;
  134. for(; i >= 0; --i){
  135. items[i].cancel();
  136. }
  137. anims.items = [];
  138. this._notify(chart, anims, Date.now(), 'complete');
  139. }
  140. remove(chart) {
  141. return this._charts.delete(chart);
  142. }
  143. }
  144. var animator = /* #__PURE__ */ new Animator();
  145. const transparent = 'transparent';
  146. const interpolators = {
  147. boolean (from, to, factor) {
  148. return factor > 0.5 ? to : from;
  149. },
  150. color (from, to, factor) {
  151. const c0 = color(from || transparent);
  152. const c1 = c0.valid && color(to || transparent);
  153. return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
  154. },
  155. number (from, to, factor) {
  156. return from + (to - from) * factor;
  157. }
  158. };
  159. class Animation {
  160. constructor(cfg, target, prop, to){
  161. const currentValue = target[prop];
  162. to = resolve([
  163. cfg.to,
  164. to,
  165. currentValue,
  166. cfg.from
  167. ]);
  168. const from = resolve([
  169. cfg.from,
  170. currentValue,
  171. to
  172. ]);
  173. this._active = true;
  174. this._fn = cfg.fn || interpolators[cfg.type || typeof from];
  175. this._easing = effects[cfg.easing] || effects.linear;
  176. this._start = Math.floor(Date.now() + (cfg.delay || 0));
  177. this._duration = this._total = Math.floor(cfg.duration);
  178. this._loop = !!cfg.loop;
  179. this._target = target;
  180. this._prop = prop;
  181. this._from = from;
  182. this._to = to;
  183. this._promises = undefined;
  184. }
  185. active() {
  186. return this._active;
  187. }
  188. update(cfg, to, date) {
  189. if (this._active) {
  190. this._notify(false);
  191. const currentValue = this._target[this._prop];
  192. const elapsed = date - this._start;
  193. const remain = this._duration - elapsed;
  194. this._start = date;
  195. this._duration = Math.floor(Math.max(remain, cfg.duration));
  196. this._total += elapsed;
  197. this._loop = !!cfg.loop;
  198. this._to = resolve([
  199. cfg.to,
  200. to,
  201. currentValue,
  202. cfg.from
  203. ]);
  204. this._from = resolve([
  205. cfg.from,
  206. currentValue,
  207. to
  208. ]);
  209. }
  210. }
  211. cancel() {
  212. if (this._active) {
  213. this.tick(Date.now());
  214. this._active = false;
  215. this._notify(false);
  216. }
  217. }
  218. tick(date) {
  219. const elapsed = date - this._start;
  220. const duration = this._duration;
  221. const prop = this._prop;
  222. const from = this._from;
  223. const loop = this._loop;
  224. const to = this._to;
  225. let factor;
  226. this._active = from !== to && (loop || elapsed < duration);
  227. if (!this._active) {
  228. this._target[prop] = to;
  229. this._notify(true);
  230. return;
  231. }
  232. if (elapsed < 0) {
  233. this._target[prop] = from;
  234. return;
  235. }
  236. factor = elapsed / duration % 2;
  237. factor = loop && factor > 1 ? 2 - factor : factor;
  238. factor = this._easing(Math.min(1, Math.max(0, factor)));
  239. this._target[prop] = this._fn(from, to, factor);
  240. }
  241. wait() {
  242. const promises = this._promises || (this._promises = []);
  243. return new Promise((res, rej)=>{
  244. promises.push({
  245. res,
  246. rej
  247. });
  248. });
  249. }
  250. _notify(resolved) {
  251. const method = resolved ? 'res' : 'rej';
  252. const promises = this._promises || [];
  253. for(let i = 0; i < promises.length; i++){
  254. promises[i][method]();
  255. }
  256. }
  257. }
  258. class Animations {
  259. constructor(chart, config){
  260. this._chart = chart;
  261. this._properties = new Map();
  262. this.configure(config);
  263. }
  264. configure(config) {
  265. if (!isObject(config)) {
  266. return;
  267. }
  268. const animationOptions = Object.keys(defaults.animation);
  269. const animatedProps = this._properties;
  270. Object.getOwnPropertyNames(config).forEach((key)=>{
  271. const cfg = config[key];
  272. if (!isObject(cfg)) {
  273. return;
  274. }
  275. const resolved = {};
  276. for (const option of animationOptions){
  277. resolved[option] = cfg[option];
  278. }
  279. (isArray(cfg.properties) && cfg.properties || [
  280. key
  281. ]).forEach((prop)=>{
  282. if (prop === key || !animatedProps.has(prop)) {
  283. animatedProps.set(prop, resolved);
  284. }
  285. });
  286. });
  287. }
  288. _animateOptions(target, values) {
  289. const newOptions = values.options;
  290. const options = resolveTargetOptions(target, newOptions);
  291. if (!options) {
  292. return [];
  293. }
  294. const animations = this._createAnimations(options, newOptions);
  295. if (newOptions.$shared) {
  296. awaitAll(target.options.$animations, newOptions).then(()=>{
  297. target.options = newOptions;
  298. }, ()=>{
  299. });
  300. }
  301. return animations;
  302. }
  303. _createAnimations(target, values) {
  304. const animatedProps = this._properties;
  305. const animations = [];
  306. const running = target.$animations || (target.$animations = {});
  307. const props = Object.keys(values);
  308. const date = Date.now();
  309. let i;
  310. for(i = props.length - 1; i >= 0; --i){
  311. const prop = props[i];
  312. if (prop.charAt(0) === '$') {
  313. continue;
  314. }
  315. if (prop === 'options') {
  316. animations.push(...this._animateOptions(target, values));
  317. continue;
  318. }
  319. const value = values[prop];
  320. let animation = running[prop];
  321. const cfg = animatedProps.get(prop);
  322. if (animation) {
  323. if (cfg && animation.active()) {
  324. animation.update(cfg, value, date);
  325. continue;
  326. } else {
  327. animation.cancel();
  328. }
  329. }
  330. if (!cfg || !cfg.duration) {
  331. target[prop] = value;
  332. continue;
  333. }
  334. running[prop] = animation = new Animation(cfg, target, prop, value);
  335. animations.push(animation);
  336. }
  337. return animations;
  338. }
  339. update(target, values) {
  340. if (this._properties.size === 0) {
  341. Object.assign(target, values);
  342. return;
  343. }
  344. const animations = this._createAnimations(target, values);
  345. if (animations.length) {
  346. animator.add(this._chart, animations);
  347. return true;
  348. }
  349. }
  350. }
  351. function awaitAll(animations, properties) {
  352. const running = [];
  353. const keys = Object.keys(properties);
  354. for(let i = 0; i < keys.length; i++){
  355. const anim = animations[keys[i]];
  356. if (anim && anim.active()) {
  357. running.push(anim.wait());
  358. }
  359. }
  360. return Promise.all(running);
  361. }
  362. function resolveTargetOptions(target, newOptions) {
  363. if (!newOptions) {
  364. return;
  365. }
  366. let options = target.options;
  367. if (!options) {
  368. target.options = newOptions;
  369. return;
  370. }
  371. if (options.$shared) {
  372. target.options = options = Object.assign({}, options, {
  373. $shared: false,
  374. $animations: {}
  375. });
  376. }
  377. return options;
  378. }
  379. function scaleClip(scale, allowedOverflow) {
  380. const opts = scale && scale.options || {};
  381. const reverse = opts.reverse;
  382. const min = opts.min === undefined ? allowedOverflow : 0;
  383. const max = opts.max === undefined ? allowedOverflow : 0;
  384. return {
  385. start: reverse ? max : min,
  386. end: reverse ? min : max
  387. };
  388. }
  389. function defaultClip(xScale, yScale, allowedOverflow) {
  390. if (allowedOverflow === false) {
  391. return false;
  392. }
  393. const x = scaleClip(xScale, allowedOverflow);
  394. const y = scaleClip(yScale, allowedOverflow);
  395. return {
  396. top: y.end,
  397. right: x.end,
  398. bottom: y.start,
  399. left: x.start
  400. };
  401. }
  402. function toClip(value) {
  403. let t, r, b, l;
  404. if (isObject(value)) {
  405. t = value.top;
  406. r = value.right;
  407. b = value.bottom;
  408. l = value.left;
  409. } else {
  410. t = r = b = l = value;
  411. }
  412. return {
  413. top: t,
  414. right: r,
  415. bottom: b,
  416. left: l,
  417. disabled: value === false
  418. };
  419. }
  420. function getSortedDatasetIndices(chart, filterVisible) {
  421. const keys = [];
  422. const metasets = chart._getSortedDatasetMetas(filterVisible);
  423. let i, ilen;
  424. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  425. keys.push(metasets[i].index);
  426. }
  427. return keys;
  428. }
  429. function applyStack(stack, value, dsIndex, options = {}) {
  430. const keys = stack.keys;
  431. const singleMode = options.mode === 'single';
  432. let i, ilen, datasetIndex, otherValue;
  433. if (value === null) {
  434. return;
  435. }
  436. for(i = 0, ilen = keys.length; i < ilen; ++i){
  437. datasetIndex = +keys[i];
  438. if (datasetIndex === dsIndex) {
  439. if (options.all) {
  440. continue;
  441. }
  442. break;
  443. }
  444. otherValue = stack.values[datasetIndex];
  445. if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) {
  446. value += otherValue;
  447. }
  448. }
  449. return value;
  450. }
  451. function convertObjectDataToArray(data) {
  452. const keys = Object.keys(data);
  453. const adata = new Array(keys.length);
  454. let i, ilen, key;
  455. for(i = 0, ilen = keys.length; i < ilen; ++i){
  456. key = keys[i];
  457. adata[i] = {
  458. x: key,
  459. y: data[key]
  460. };
  461. }
  462. return adata;
  463. }
  464. function isStacked(scale, meta) {
  465. const stacked = scale && scale.options.stacked;
  466. return stacked || stacked === undefined && meta.stack !== undefined;
  467. }
  468. function getStackKey(indexScale, valueScale, meta) {
  469. return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
  470. }
  471. function getUserBounds(scale) {
  472. const { min , max , minDefined , maxDefined } = scale.getUserBounds();
  473. return {
  474. min: minDefined ? min : Number.NEGATIVE_INFINITY,
  475. max: maxDefined ? max : Number.POSITIVE_INFINITY
  476. };
  477. }
  478. function getOrCreateStack(stacks, stackKey, indexValue) {
  479. const subStack = stacks[stackKey] || (stacks[stackKey] = {});
  480. return subStack[indexValue] || (subStack[indexValue] = {});
  481. }
  482. function getLastIndexInStack(stack, vScale, positive, type) {
  483. for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
  484. const value = stack[meta.index];
  485. if (positive && value > 0 || !positive && value < 0) {
  486. return meta.index;
  487. }
  488. }
  489. return null;
  490. }
  491. function updateStacks(controller, parsed) {
  492. const { chart , _cachedMeta: meta } = controller;
  493. const stacks = chart._stacks || (chart._stacks = {});
  494. const { iScale , vScale , index: datasetIndex } = meta;
  495. const iAxis = iScale.axis;
  496. const vAxis = vScale.axis;
  497. const key = getStackKey(iScale, vScale, meta);
  498. const ilen = parsed.length;
  499. let stack;
  500. for(let i = 0; i < ilen; ++i){
  501. const item = parsed[i];
  502. const { [iAxis]: index , [vAxis]: value } = item;
  503. const itemStacks = item._stacks || (item._stacks = {});
  504. stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
  505. stack[datasetIndex] = value;
  506. stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
  507. stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
  508. const visualValues = stack._visualValues || (stack._visualValues = {});
  509. visualValues[datasetIndex] = value;
  510. }
  511. }
  512. function getFirstScaleId(chart, axis) {
  513. const scales = chart.scales;
  514. return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
  515. }
  516. function createDatasetContext(parent, index) {
  517. return createContext(parent, {
  518. active: false,
  519. dataset: undefined,
  520. datasetIndex: index,
  521. index,
  522. mode: 'default',
  523. type: 'dataset'
  524. });
  525. }
  526. function createDataContext(parent, index, element) {
  527. return createContext(parent, {
  528. active: false,
  529. dataIndex: index,
  530. parsed: undefined,
  531. raw: undefined,
  532. element,
  533. index,
  534. mode: 'default',
  535. type: 'data'
  536. });
  537. }
  538. function clearStacks(meta, items) {
  539. const datasetIndex = meta.controller.index;
  540. const axis = meta.vScale && meta.vScale.axis;
  541. if (!axis) {
  542. return;
  543. }
  544. items = items || meta._parsed;
  545. for (const parsed of items){
  546. const stacks = parsed._stacks;
  547. if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
  548. return;
  549. }
  550. delete stacks[axis][datasetIndex];
  551. if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
  552. delete stacks[axis]._visualValues[datasetIndex];
  553. }
  554. }
  555. }
  556. const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
  557. const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
  558. const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
  559. keys: getSortedDatasetIndices(chart, true),
  560. values: null
  561. };
  562. class DatasetController {
  563. static defaults = {};
  564. static datasetElementType = null;
  565. static dataElementType = null;
  566. constructor(chart, datasetIndex){
  567. this.chart = chart;
  568. this._ctx = chart.ctx;
  569. this.index = datasetIndex;
  570. this._cachedDataOpts = {};
  571. this._cachedMeta = this.getMeta();
  572. this._type = this._cachedMeta.type;
  573. this.options = undefined;
  574. this._parsing = false;
  575. this._data = undefined;
  576. this._objectData = undefined;
  577. this._sharedOptions = undefined;
  578. this._drawStart = undefined;
  579. this._drawCount = undefined;
  580. this.enableOptionSharing = false;
  581. this.supportsDecimation = false;
  582. this.$context = undefined;
  583. this._syncList = [];
  584. this.datasetElementType = new.target.datasetElementType;
  585. this.dataElementType = new.target.dataElementType;
  586. this.initialize();
  587. }
  588. initialize() {
  589. const meta = this._cachedMeta;
  590. this.configure();
  591. this.linkScales();
  592. meta._stacked = isStacked(meta.vScale, meta);
  593. this.addElements();
  594. if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
  595. console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
  596. }
  597. }
  598. updateIndex(datasetIndex) {
  599. if (this.index !== datasetIndex) {
  600. clearStacks(this._cachedMeta);
  601. }
  602. this.index = datasetIndex;
  603. }
  604. linkScales() {
  605. const chart = this.chart;
  606. const meta = this._cachedMeta;
  607. const dataset = this.getDataset();
  608. const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
  609. const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
  610. const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
  611. const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
  612. const indexAxis = meta.indexAxis;
  613. const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
  614. const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
  615. meta.xScale = this.getScaleForId(xid);
  616. meta.yScale = this.getScaleForId(yid);
  617. meta.rScale = this.getScaleForId(rid);
  618. meta.iScale = this.getScaleForId(iid);
  619. meta.vScale = this.getScaleForId(vid);
  620. }
  621. getDataset() {
  622. return this.chart.data.datasets[this.index];
  623. }
  624. getMeta() {
  625. return this.chart.getDatasetMeta(this.index);
  626. }
  627. getScaleForId(scaleID) {
  628. return this.chart.scales[scaleID];
  629. }
  630. _getOtherScale(scale) {
  631. const meta = this._cachedMeta;
  632. return scale === meta.iScale ? meta.vScale : meta.iScale;
  633. }
  634. reset() {
  635. this._update('reset');
  636. }
  637. _destroy() {
  638. const meta = this._cachedMeta;
  639. if (this._data) {
  640. unlistenArrayEvents(this._data, this);
  641. }
  642. if (meta._stacked) {
  643. clearStacks(meta);
  644. }
  645. }
  646. _dataCheck() {
  647. const dataset = this.getDataset();
  648. const data = dataset.data || (dataset.data = []);
  649. const _data = this._data;
  650. if (isObject(data)) {
  651. this._data = convertObjectDataToArray(data);
  652. } else if (_data !== data) {
  653. if (_data) {
  654. unlistenArrayEvents(_data, this);
  655. const meta = this._cachedMeta;
  656. clearStacks(meta);
  657. meta._parsed = [];
  658. }
  659. if (data && Object.isExtensible(data)) {
  660. listenArrayEvents(data, this);
  661. }
  662. this._syncList = [];
  663. this._data = data;
  664. }
  665. }
  666. addElements() {
  667. const meta = this._cachedMeta;
  668. this._dataCheck();
  669. if (this.datasetElementType) {
  670. meta.dataset = new this.datasetElementType();
  671. }
  672. }
  673. buildOrUpdateElements(resetNewElements) {
  674. const meta = this._cachedMeta;
  675. const dataset = this.getDataset();
  676. let stackChanged = false;
  677. this._dataCheck();
  678. const oldStacked = meta._stacked;
  679. meta._stacked = isStacked(meta.vScale, meta);
  680. if (meta.stack !== dataset.stack) {
  681. stackChanged = true;
  682. clearStacks(meta);
  683. meta.stack = dataset.stack;
  684. }
  685. this._resyncElements(resetNewElements);
  686. if (stackChanged || oldStacked !== meta._stacked) {
  687. updateStacks(this, meta._parsed);
  688. }
  689. }
  690. configure() {
  691. const config = this.chart.config;
  692. const scopeKeys = config.datasetScopeKeys(this._type);
  693. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
  694. this.options = config.createResolver(scopes, this.getContext());
  695. this._parsing = this.options.parsing;
  696. this._cachedDataOpts = {};
  697. }
  698. parse(start, count) {
  699. const { _cachedMeta: meta , _data: data } = this;
  700. const { iScale , _stacked } = meta;
  701. const iAxis = iScale.axis;
  702. let sorted = start === 0 && count === data.length ? true : meta._sorted;
  703. let prev = start > 0 && meta._parsed[start - 1];
  704. let i, cur, parsed;
  705. if (this._parsing === false) {
  706. meta._parsed = data;
  707. meta._sorted = true;
  708. parsed = data;
  709. } else {
  710. if (isArray(data[start])) {
  711. parsed = this.parseArrayData(meta, data, start, count);
  712. } else if (isObject(data[start])) {
  713. parsed = this.parseObjectData(meta, data, start, count);
  714. } else {
  715. parsed = this.parsePrimitiveData(meta, data, start, count);
  716. }
  717. const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
  718. for(i = 0; i < count; ++i){
  719. meta._parsed[i + start] = cur = parsed[i];
  720. if (sorted) {
  721. if (isNotInOrderComparedToPrev()) {
  722. sorted = false;
  723. }
  724. prev = cur;
  725. }
  726. }
  727. meta._sorted = sorted;
  728. }
  729. if (_stacked) {
  730. updateStacks(this, parsed);
  731. }
  732. }
  733. parsePrimitiveData(meta, data, start, count) {
  734. const { iScale , vScale } = meta;
  735. const iAxis = iScale.axis;
  736. const vAxis = vScale.axis;
  737. const labels = iScale.getLabels();
  738. const singleScale = iScale === vScale;
  739. const parsed = new Array(count);
  740. let i, ilen, index;
  741. for(i = 0, ilen = count; i < ilen; ++i){
  742. index = i + start;
  743. parsed[i] = {
  744. [iAxis]: singleScale || iScale.parse(labels[index], index),
  745. [vAxis]: vScale.parse(data[index], index)
  746. };
  747. }
  748. return parsed;
  749. }
  750. parseArrayData(meta, data, start, count) {
  751. const { xScale , yScale } = meta;
  752. const parsed = new Array(count);
  753. let i, ilen, index, item;
  754. for(i = 0, ilen = count; i < ilen; ++i){
  755. index = i + start;
  756. item = data[index];
  757. parsed[i] = {
  758. x: xScale.parse(item[0], index),
  759. y: yScale.parse(item[1], index)
  760. };
  761. }
  762. return parsed;
  763. }
  764. parseObjectData(meta, data, start, count) {
  765. const { xScale , yScale } = meta;
  766. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  767. const parsed = new Array(count);
  768. let i, ilen, index, item;
  769. for(i = 0, ilen = count; i < ilen; ++i){
  770. index = i + start;
  771. item = data[index];
  772. parsed[i] = {
  773. x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
  774. y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
  775. };
  776. }
  777. return parsed;
  778. }
  779. getParsed(index) {
  780. return this._cachedMeta._parsed[index];
  781. }
  782. getDataElement(index) {
  783. return this._cachedMeta.data[index];
  784. }
  785. applyStack(scale, parsed, mode) {
  786. const chart = this.chart;
  787. const meta = this._cachedMeta;
  788. const value = parsed[scale.axis];
  789. const stack = {
  790. keys: getSortedDatasetIndices(chart, true),
  791. values: parsed._stacks[scale.axis]._visualValues
  792. };
  793. return applyStack(stack, value, meta.index, {
  794. mode
  795. });
  796. }
  797. updateRangeFromParsed(range, scale, parsed, stack) {
  798. const parsedValue = parsed[scale.axis];
  799. let value = parsedValue === null ? NaN : parsedValue;
  800. const values = stack && parsed._stacks[scale.axis];
  801. if (stack && values) {
  802. stack.values = values;
  803. value = applyStack(stack, parsedValue, this._cachedMeta.index);
  804. }
  805. range.min = Math.min(range.min, value);
  806. range.max = Math.max(range.max, value);
  807. }
  808. getMinMax(scale, canStack) {
  809. const meta = this._cachedMeta;
  810. const _parsed = meta._parsed;
  811. const sorted = meta._sorted && scale === meta.iScale;
  812. const ilen = _parsed.length;
  813. const otherScale = this._getOtherScale(scale);
  814. const stack = createStack(canStack, meta, this.chart);
  815. const range = {
  816. min: Number.POSITIVE_INFINITY,
  817. max: Number.NEGATIVE_INFINITY
  818. };
  819. const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
  820. let i, parsed;
  821. function _skip() {
  822. parsed = _parsed[i];
  823. const otherValue = parsed[otherScale.axis];
  824. return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
  825. }
  826. for(i = 0; i < ilen; ++i){
  827. if (_skip()) {
  828. continue;
  829. }
  830. this.updateRangeFromParsed(range, scale, parsed, stack);
  831. if (sorted) {
  832. break;
  833. }
  834. }
  835. if (sorted) {
  836. for(i = ilen - 1; i >= 0; --i){
  837. if (_skip()) {
  838. continue;
  839. }
  840. this.updateRangeFromParsed(range, scale, parsed, stack);
  841. break;
  842. }
  843. }
  844. return range;
  845. }
  846. getAllParsedValues(scale) {
  847. const parsed = this._cachedMeta._parsed;
  848. const values = [];
  849. let i, ilen, value;
  850. for(i = 0, ilen = parsed.length; i < ilen; ++i){
  851. value = parsed[i][scale.axis];
  852. if (isNumberFinite(value)) {
  853. values.push(value);
  854. }
  855. }
  856. return values;
  857. }
  858. getMaxOverflow() {
  859. return false;
  860. }
  861. getLabelAndValue(index) {
  862. const meta = this._cachedMeta;
  863. const iScale = meta.iScale;
  864. const vScale = meta.vScale;
  865. const parsed = this.getParsed(index);
  866. return {
  867. label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
  868. value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
  869. };
  870. }
  871. _update(mode) {
  872. const meta = this._cachedMeta;
  873. this.update(mode || 'default');
  874. meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
  875. }
  876. update(mode) {}
  877. draw() {
  878. const ctx = this._ctx;
  879. const chart = this.chart;
  880. const meta = this._cachedMeta;
  881. const elements = meta.data || [];
  882. const area = chart.chartArea;
  883. const active = [];
  884. const start = this._drawStart || 0;
  885. const count = this._drawCount || elements.length - start;
  886. const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
  887. let i;
  888. if (meta.dataset) {
  889. meta.dataset.draw(ctx, area, start, count);
  890. }
  891. for(i = start; i < start + count; ++i){
  892. const element = elements[i];
  893. if (element.hidden) {
  894. continue;
  895. }
  896. if (element.active && drawActiveElementsOnTop) {
  897. active.push(element);
  898. } else {
  899. element.draw(ctx, area);
  900. }
  901. }
  902. for(i = 0; i < active.length; ++i){
  903. active[i].draw(ctx, area);
  904. }
  905. }
  906. getStyle(index, active) {
  907. const mode = active ? 'active' : 'default';
  908. return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
  909. }
  910. getContext(index, active, mode) {
  911. const dataset = this.getDataset();
  912. let context;
  913. if (index >= 0 && index < this._cachedMeta.data.length) {
  914. const element = this._cachedMeta.data[index];
  915. context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
  916. context.parsed = this.getParsed(index);
  917. context.raw = dataset.data[index];
  918. context.index = context.dataIndex = index;
  919. } else {
  920. context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
  921. context.dataset = dataset;
  922. context.index = context.datasetIndex = this.index;
  923. }
  924. context.active = !!active;
  925. context.mode = mode;
  926. return context;
  927. }
  928. resolveDatasetElementOptions(mode) {
  929. return this._resolveElementOptions(this.datasetElementType.id, mode);
  930. }
  931. resolveDataElementOptions(index, mode) {
  932. return this._resolveElementOptions(this.dataElementType.id, mode, index);
  933. }
  934. _resolveElementOptions(elementType, mode = 'default', index) {
  935. const active = mode === 'active';
  936. const cache = this._cachedDataOpts;
  937. const cacheKey = elementType + '-' + mode;
  938. const cached = cache[cacheKey];
  939. const sharing = this.enableOptionSharing && defined(index);
  940. if (cached) {
  941. return cloneIfNotShared(cached, sharing);
  942. }
  943. const config = this.chart.config;
  944. const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
  945. const prefixes = active ? [
  946. `${elementType}Hover`,
  947. 'hover',
  948. elementType,
  949. ''
  950. ] : [
  951. elementType,
  952. ''
  953. ];
  954. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  955. const names = Object.keys(defaults.elements[elementType]);
  956. const context = ()=>this.getContext(index, active, mode);
  957. const values = config.resolveNamedOptions(scopes, names, context, prefixes);
  958. if (values.$shared) {
  959. values.$shared = sharing;
  960. cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
  961. }
  962. return values;
  963. }
  964. _resolveAnimations(index, transition, active) {
  965. const chart = this.chart;
  966. const cache = this._cachedDataOpts;
  967. const cacheKey = `animation-${transition}`;
  968. const cached = cache[cacheKey];
  969. if (cached) {
  970. return cached;
  971. }
  972. let options;
  973. if (chart.options.animation !== false) {
  974. const config = this.chart.config;
  975. const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
  976. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  977. options = config.createResolver(scopes, this.getContext(index, active, transition));
  978. }
  979. const animations = new Animations(chart, options && options.animations);
  980. if (options && options._cacheable) {
  981. cache[cacheKey] = Object.freeze(animations);
  982. }
  983. return animations;
  984. }
  985. getSharedOptions(options) {
  986. if (!options.$shared) {
  987. return;
  988. }
  989. return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
  990. }
  991. includeOptions(mode, sharedOptions) {
  992. return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
  993. }
  994. _getSharedOptions(start, mode) {
  995. const firstOpts = this.resolveDataElementOptions(start, mode);
  996. const previouslySharedOptions = this._sharedOptions;
  997. const sharedOptions = this.getSharedOptions(firstOpts);
  998. const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
  999. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  1000. return {
  1001. sharedOptions,
  1002. includeOptions
  1003. };
  1004. }
  1005. updateElement(element, index, properties, mode) {
  1006. if (isDirectUpdateMode(mode)) {
  1007. Object.assign(element, properties);
  1008. } else {
  1009. this._resolveAnimations(index, mode).update(element, properties);
  1010. }
  1011. }
  1012. updateSharedOptions(sharedOptions, mode, newOptions) {
  1013. if (sharedOptions && !isDirectUpdateMode(mode)) {
  1014. this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
  1015. }
  1016. }
  1017. _setStyle(element, index, mode, active) {
  1018. element.active = active;
  1019. const options = this.getStyle(index, active);
  1020. this._resolveAnimations(index, mode, active).update(element, {
  1021. options: !active && this.getSharedOptions(options) || options
  1022. });
  1023. }
  1024. removeHoverStyle(element, datasetIndex, index) {
  1025. this._setStyle(element, index, 'active', false);
  1026. }
  1027. setHoverStyle(element, datasetIndex, index) {
  1028. this._setStyle(element, index, 'active', true);
  1029. }
  1030. _removeDatasetHoverStyle() {
  1031. const element = this._cachedMeta.dataset;
  1032. if (element) {
  1033. this._setStyle(element, undefined, 'active', false);
  1034. }
  1035. }
  1036. _setDatasetHoverStyle() {
  1037. const element = this._cachedMeta.dataset;
  1038. if (element) {
  1039. this._setStyle(element, undefined, 'active', true);
  1040. }
  1041. }
  1042. _resyncElements(resetNewElements) {
  1043. const data = this._data;
  1044. const elements = this._cachedMeta.data;
  1045. for (const [method, arg1, arg2] of this._syncList){
  1046. this[method](arg1, arg2);
  1047. }
  1048. this._syncList = [];
  1049. const numMeta = elements.length;
  1050. const numData = data.length;
  1051. const count = Math.min(numData, numMeta);
  1052. if (count) {
  1053. this.parse(0, count);
  1054. }
  1055. if (numData > numMeta) {
  1056. this._insertElements(numMeta, numData - numMeta, resetNewElements);
  1057. } else if (numData < numMeta) {
  1058. this._removeElements(numData, numMeta - numData);
  1059. }
  1060. }
  1061. _insertElements(start, count, resetNewElements = true) {
  1062. const meta = this._cachedMeta;
  1063. const data = meta.data;
  1064. const end = start + count;
  1065. let i;
  1066. const move = (arr)=>{
  1067. arr.length += count;
  1068. for(i = arr.length - 1; i >= end; i--){
  1069. arr[i] = arr[i - count];
  1070. }
  1071. };
  1072. move(data);
  1073. for(i = start; i < end; ++i){
  1074. data[i] = new this.dataElementType();
  1075. }
  1076. if (this._parsing) {
  1077. move(meta._parsed);
  1078. }
  1079. this.parse(start, count);
  1080. if (resetNewElements) {
  1081. this.updateElements(data, start, count, 'reset');
  1082. }
  1083. }
  1084. updateElements(element, start, count, mode) {}
  1085. _removeElements(start, count) {
  1086. const meta = this._cachedMeta;
  1087. if (this._parsing) {
  1088. const removed = meta._parsed.splice(start, count);
  1089. if (meta._stacked) {
  1090. clearStacks(meta, removed);
  1091. }
  1092. }
  1093. meta.data.splice(start, count);
  1094. }
  1095. _sync(args) {
  1096. if (this._parsing) {
  1097. this._syncList.push(args);
  1098. } else {
  1099. const [method, arg1, arg2] = args;
  1100. this[method](arg1, arg2);
  1101. }
  1102. this.chart._dataChanges.push([
  1103. this.index,
  1104. ...args
  1105. ]);
  1106. }
  1107. _onDataPush() {
  1108. const count = arguments.length;
  1109. this._sync([
  1110. '_insertElements',
  1111. this.getDataset().data.length - count,
  1112. count
  1113. ]);
  1114. }
  1115. _onDataPop() {
  1116. this._sync([
  1117. '_removeElements',
  1118. this._cachedMeta.data.length - 1,
  1119. 1
  1120. ]);
  1121. }
  1122. _onDataShift() {
  1123. this._sync([
  1124. '_removeElements',
  1125. 0,
  1126. 1
  1127. ]);
  1128. }
  1129. _onDataSplice(start, count) {
  1130. if (count) {
  1131. this._sync([
  1132. '_removeElements',
  1133. start,
  1134. count
  1135. ]);
  1136. }
  1137. const newCount = arguments.length - 2;
  1138. if (newCount) {
  1139. this._sync([
  1140. '_insertElements',
  1141. start,
  1142. newCount
  1143. ]);
  1144. }
  1145. }
  1146. _onDataUnshift() {
  1147. this._sync([
  1148. '_insertElements',
  1149. 0,
  1150. arguments.length
  1151. ]);
  1152. }
  1153. }
  1154. function getAllScaleValues(scale, type) {
  1155. if (!scale._cache.$bar) {
  1156. const visibleMetas = scale.getMatchingVisibleMetas(type);
  1157. let values = [];
  1158. for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
  1159. values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
  1160. }
  1161. scale._cache.$bar = _arrayUnique(values.sort((a, b)=>a - b));
  1162. }
  1163. return scale._cache.$bar;
  1164. }
  1165. function computeMinSampleSize(meta) {
  1166. const scale = meta.iScale;
  1167. const values = getAllScaleValues(scale, meta.type);
  1168. let min = scale._length;
  1169. let i, ilen, curr, prev;
  1170. const updateMinAndPrev = ()=>{
  1171. if (curr === 32767 || curr === -32768) {
  1172. return;
  1173. }
  1174. if (defined(prev)) {
  1175. min = Math.min(min, Math.abs(curr - prev) || min);
  1176. }
  1177. prev = curr;
  1178. };
  1179. for(i = 0, ilen = values.length; i < ilen; ++i){
  1180. curr = scale.getPixelForValue(values[i]);
  1181. updateMinAndPrev();
  1182. }
  1183. prev = undefined;
  1184. for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
  1185. curr = scale.getPixelForTick(i);
  1186. updateMinAndPrev();
  1187. }
  1188. return min;
  1189. }
  1190. function computeFitCategoryTraits(index, ruler, options, stackCount) {
  1191. const thickness = options.barThickness;
  1192. let size, ratio;
  1193. if (isNullOrUndef(thickness)) {
  1194. size = ruler.min * options.categoryPercentage;
  1195. ratio = options.barPercentage;
  1196. } else {
  1197. size = thickness * stackCount;
  1198. ratio = 1;
  1199. }
  1200. return {
  1201. chunk: size / stackCount,
  1202. ratio,
  1203. start: ruler.pixels[index] - size / 2
  1204. };
  1205. }
  1206. function computeFlexCategoryTraits(index, ruler, options, stackCount) {
  1207. const pixels = ruler.pixels;
  1208. const curr = pixels[index];
  1209. let prev = index > 0 ? pixels[index - 1] : null;
  1210. let next = index < pixels.length - 1 ? pixels[index + 1] : null;
  1211. const percent = options.categoryPercentage;
  1212. if (prev === null) {
  1213. prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
  1214. }
  1215. if (next === null) {
  1216. next = curr + curr - prev;
  1217. }
  1218. const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
  1219. const size = Math.abs(next - prev) / 2 * percent;
  1220. return {
  1221. chunk: size / stackCount,
  1222. ratio: options.barPercentage,
  1223. start
  1224. };
  1225. }
  1226. function parseFloatBar(entry, item, vScale, i) {
  1227. const startValue = vScale.parse(entry[0], i);
  1228. const endValue = vScale.parse(entry[1], i);
  1229. const min = Math.min(startValue, endValue);
  1230. const max = Math.max(startValue, endValue);
  1231. let barStart = min;
  1232. let barEnd = max;
  1233. if (Math.abs(min) > Math.abs(max)) {
  1234. barStart = max;
  1235. barEnd = min;
  1236. }
  1237. item[vScale.axis] = barEnd;
  1238. item._custom = {
  1239. barStart,
  1240. barEnd,
  1241. start: startValue,
  1242. end: endValue,
  1243. min,
  1244. max
  1245. };
  1246. }
  1247. function parseValue(entry, item, vScale, i) {
  1248. if (isArray(entry)) {
  1249. parseFloatBar(entry, item, vScale, i);
  1250. } else {
  1251. item[vScale.axis] = vScale.parse(entry, i);
  1252. }
  1253. return item;
  1254. }
  1255. function parseArrayOrPrimitive(meta, data, start, count) {
  1256. const iScale = meta.iScale;
  1257. const vScale = meta.vScale;
  1258. const labels = iScale.getLabels();
  1259. const singleScale = iScale === vScale;
  1260. const parsed = [];
  1261. let i, ilen, item, entry;
  1262. for(i = start, ilen = start + count; i < ilen; ++i){
  1263. entry = data[i];
  1264. item = {};
  1265. item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
  1266. parsed.push(parseValue(entry, item, vScale, i));
  1267. }
  1268. return parsed;
  1269. }
  1270. function isFloatBar(custom) {
  1271. return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  1272. }
  1273. function barSign(size, vScale, actualBase) {
  1274. if (size !== 0) {
  1275. return sign(size);
  1276. }
  1277. return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  1278. }
  1279. function borderProps(properties) {
  1280. let reverse, start, end, top, bottom;
  1281. if (properties.horizontal) {
  1282. reverse = properties.base > properties.x;
  1283. start = 'left';
  1284. end = 'right';
  1285. } else {
  1286. reverse = properties.base < properties.y;
  1287. start = 'bottom';
  1288. end = 'top';
  1289. }
  1290. if (reverse) {
  1291. top = 'end';
  1292. bottom = 'start';
  1293. } else {
  1294. top = 'start';
  1295. bottom = 'end';
  1296. }
  1297. return {
  1298. start,
  1299. end,
  1300. reverse,
  1301. top,
  1302. bottom
  1303. };
  1304. }
  1305. function setBorderSkipped(properties, options, stack, index) {
  1306. let edge = options.borderSkipped;
  1307. const res = {};
  1308. if (!edge) {
  1309. properties.borderSkipped = res;
  1310. return;
  1311. }
  1312. if (edge === true) {
  1313. properties.borderSkipped = {
  1314. top: true,
  1315. right: true,
  1316. bottom: true,
  1317. left: true
  1318. };
  1319. return;
  1320. }
  1321. const { start , end , reverse , top , bottom } = borderProps(properties);
  1322. if (edge === 'middle' && stack) {
  1323. properties.enableBorderRadius = true;
  1324. if ((stack._top || 0) === index) {
  1325. edge = top;
  1326. } else if ((stack._bottom || 0) === index) {
  1327. edge = bottom;
  1328. } else {
  1329. res[parseEdge(bottom, start, end, reverse)] = true;
  1330. edge = top;
  1331. }
  1332. }
  1333. res[parseEdge(edge, start, end, reverse)] = true;
  1334. properties.borderSkipped = res;
  1335. }
  1336. function parseEdge(edge, a, b, reverse) {
  1337. if (reverse) {
  1338. edge = swap(edge, a, b);
  1339. edge = startEnd(edge, b, a);
  1340. } else {
  1341. edge = startEnd(edge, a, b);
  1342. }
  1343. return edge;
  1344. }
  1345. function swap(orig, v1, v2) {
  1346. return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  1347. }
  1348. function startEnd(v, start, end) {
  1349. return v === 'start' ? start : v === 'end' ? end : v;
  1350. }
  1351. function setInflateAmount(properties, { inflateAmount }, ratio) {
  1352. properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
  1353. }
  1354. class BarController extends DatasetController {
  1355. static id = 'bar';
  1356. static defaults = {
  1357. datasetElementType: false,
  1358. dataElementType: 'bar',
  1359. categoryPercentage: 0.8,
  1360. barPercentage: 0.9,
  1361. grouped: true,
  1362. animations: {
  1363. numbers: {
  1364. type: 'number',
  1365. properties: [
  1366. 'x',
  1367. 'y',
  1368. 'base',
  1369. 'width',
  1370. 'height'
  1371. ]
  1372. }
  1373. }
  1374. };
  1375. static overrides = {
  1376. scales: {
  1377. _index_: {
  1378. type: 'category',
  1379. offset: true,
  1380. grid: {
  1381. offset: true
  1382. }
  1383. },
  1384. _value_: {
  1385. type: 'linear',
  1386. beginAtZero: true
  1387. }
  1388. }
  1389. };
  1390. parsePrimitiveData(meta, data, start, count) {
  1391. return parseArrayOrPrimitive(meta, data, start, count);
  1392. }
  1393. parseArrayData(meta, data, start, count) {
  1394. return parseArrayOrPrimitive(meta, data, start, count);
  1395. }
  1396. parseObjectData(meta, data, start, count) {
  1397. const { iScale , vScale } = meta;
  1398. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  1399. const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
  1400. const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
  1401. const parsed = [];
  1402. let i, ilen, item, obj;
  1403. for(i = start, ilen = start + count; i < ilen; ++i){
  1404. obj = data[i];
  1405. item = {};
  1406. item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
  1407. parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
  1408. }
  1409. return parsed;
  1410. }
  1411. updateRangeFromParsed(range, scale, parsed, stack) {
  1412. super.updateRangeFromParsed(range, scale, parsed, stack);
  1413. const custom = parsed._custom;
  1414. if (custom && scale === this._cachedMeta.vScale) {
  1415. range.min = Math.min(range.min, custom.min);
  1416. range.max = Math.max(range.max, custom.max);
  1417. }
  1418. }
  1419. getMaxOverflow() {
  1420. return 0;
  1421. }
  1422. getLabelAndValue(index) {
  1423. const meta = this._cachedMeta;
  1424. const { iScale , vScale } = meta;
  1425. const parsed = this.getParsed(index);
  1426. const custom = parsed._custom;
  1427. const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
  1428. return {
  1429. label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
  1430. value
  1431. };
  1432. }
  1433. initialize() {
  1434. this.enableOptionSharing = true;
  1435. super.initialize();
  1436. const meta = this._cachedMeta;
  1437. meta.stack = this.getDataset().stack;
  1438. }
  1439. update(mode) {
  1440. const meta = this._cachedMeta;
  1441. this.updateElements(meta.data, 0, meta.data.length, mode);
  1442. }
  1443. updateElements(bars, start, count, mode) {
  1444. const reset = mode === 'reset';
  1445. const { index , _cachedMeta: { vScale } } = this;
  1446. const base = vScale.getBasePixel();
  1447. const horizontal = vScale.isHorizontal();
  1448. const ruler = this._getRuler();
  1449. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1450. for(let i = start; i < start + count; i++){
  1451. const parsed = this.getParsed(i);
  1452. const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {
  1453. base,
  1454. head: base
  1455. } : this._calculateBarValuePixels(i);
  1456. const ipixels = this._calculateBarIndexPixels(i, ruler);
  1457. const stack = (parsed._stacks || {})[vScale.axis];
  1458. const properties = {
  1459. horizontal,
  1460. base: vpixels.base,
  1461. enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
  1462. x: horizontal ? vpixels.head : ipixels.center,
  1463. y: horizontal ? ipixels.center : vpixels.head,
  1464. height: horizontal ? ipixels.size : Math.abs(vpixels.size),
  1465. width: horizontal ? Math.abs(vpixels.size) : ipixels.size
  1466. };
  1467. if (includeOptions) {
  1468. properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
  1469. }
  1470. const options = properties.options || bars[i].options;
  1471. setBorderSkipped(properties, options, stack, index);
  1472. setInflateAmount(properties, options, ruler.ratio);
  1473. this.updateElement(bars[i], i, properties, mode);
  1474. }
  1475. }
  1476. _getStacks(last, dataIndex) {
  1477. const { iScale } = this._cachedMeta;
  1478. const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
  1479. const stacked = iScale.options.stacked;
  1480. const stacks = [];
  1481. const skipNull = (meta)=>{
  1482. const parsed = meta.controller.getParsed(dataIndex);
  1483. const val = parsed && parsed[meta.vScale.axis];
  1484. if (isNullOrUndef(val) || isNaN(val)) {
  1485. return true;
  1486. }
  1487. };
  1488. for (const meta of metasets){
  1489. if (dataIndex !== undefined && skipNull(meta)) {
  1490. continue;
  1491. }
  1492. if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
  1493. stacks.push(meta.stack);
  1494. }
  1495. if (meta.index === last) {
  1496. break;
  1497. }
  1498. }
  1499. if (!stacks.length) {
  1500. stacks.push(undefined);
  1501. }
  1502. return stacks;
  1503. }
  1504. _getStackCount(index) {
  1505. return this._getStacks(undefined, index).length;
  1506. }
  1507. _getStackIndex(datasetIndex, name, dataIndex) {
  1508. const stacks = this._getStacks(datasetIndex, dataIndex);
  1509. const index = name !== undefined ? stacks.indexOf(name) : -1;
  1510. return index === -1 ? stacks.length - 1 : index;
  1511. }
  1512. _getRuler() {
  1513. const opts = this.options;
  1514. const meta = this._cachedMeta;
  1515. const iScale = meta.iScale;
  1516. const pixels = [];
  1517. let i, ilen;
  1518. for(i = 0, ilen = meta.data.length; i < ilen; ++i){
  1519. pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
  1520. }
  1521. const barThickness = opts.barThickness;
  1522. const min = barThickness || computeMinSampleSize(meta);
  1523. return {
  1524. min,
  1525. pixels,
  1526. start: iScale._startPixel,
  1527. end: iScale._endPixel,
  1528. stackCount: this._getStackCount(),
  1529. scale: iScale,
  1530. grouped: opts.grouped,
  1531. ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
  1532. };
  1533. }
  1534. _calculateBarValuePixels(index) {
  1535. const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
  1536. const actualBase = baseValue || 0;
  1537. const parsed = this.getParsed(index);
  1538. const custom = parsed._custom;
  1539. const floating = isFloatBar(custom);
  1540. let value = parsed[vScale.axis];
  1541. let start = 0;
  1542. let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
  1543. let head, size;
  1544. if (length !== value) {
  1545. start = length - value;
  1546. length = value;
  1547. }
  1548. if (floating) {
  1549. value = custom.barStart;
  1550. length = custom.barEnd - custom.barStart;
  1551. if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
  1552. start = 0;
  1553. }
  1554. start += value;
  1555. }
  1556. const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
  1557. let base = vScale.getPixelForValue(startValue);
  1558. if (this.chart.getDataVisibility(index)) {
  1559. head = vScale.getPixelForValue(start + length);
  1560. } else {
  1561. head = base;
  1562. }
  1563. size = head - base;
  1564. if (Math.abs(size) < minBarLength) {
  1565. size = barSign(size, vScale, actualBase) * minBarLength;
  1566. if (value === actualBase) {
  1567. base -= size / 2;
  1568. }
  1569. const startPixel = vScale.getPixelForDecimal(0);
  1570. const endPixel = vScale.getPixelForDecimal(1);
  1571. const min = Math.min(startPixel, endPixel);
  1572. const max = Math.max(startPixel, endPixel);
  1573. base = Math.max(Math.min(base, max), min);
  1574. head = base + size;
  1575. if (_stacked && !floating) {
  1576. parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
  1577. }
  1578. }
  1579. if (base === vScale.getPixelForValue(actualBase)) {
  1580. const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
  1581. base += halfGrid;
  1582. size -= halfGrid;
  1583. }
  1584. return {
  1585. size,
  1586. base,
  1587. head,
  1588. center: head + size / 2
  1589. };
  1590. }
  1591. _calculateBarIndexPixels(index, ruler) {
  1592. const scale = ruler.scale;
  1593. const options = this.options;
  1594. const skipNull = options.skipNull;
  1595. const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
  1596. let center, size;
  1597. if (ruler.grouped) {
  1598. const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
  1599. const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
  1600. const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
  1601. center = range.start + range.chunk * stackIndex + range.chunk / 2;
  1602. size = Math.min(maxBarThickness, range.chunk * range.ratio);
  1603. } else {
  1604. center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
  1605. size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
  1606. }
  1607. return {
  1608. base: center - size / 2,
  1609. head: center + size / 2,
  1610. center,
  1611. size
  1612. };
  1613. }
  1614. draw() {
  1615. const meta = this._cachedMeta;
  1616. const vScale = meta.vScale;
  1617. const rects = meta.data;
  1618. const ilen = rects.length;
  1619. let i = 0;
  1620. for(; i < ilen; ++i){
  1621. if (this.getParsed(i)[vScale.axis] !== null) {
  1622. rects[i].draw(this._ctx);
  1623. }
  1624. }
  1625. }
  1626. }
  1627. class BubbleController extends DatasetController {
  1628. static id = 'bubble';
  1629. static defaults = {
  1630. datasetElementType: false,
  1631. dataElementType: 'point',
  1632. animations: {
  1633. numbers: {
  1634. type: 'number',
  1635. properties: [
  1636. 'x',
  1637. 'y',
  1638. 'borderWidth',
  1639. 'radius'
  1640. ]
  1641. }
  1642. }
  1643. };
  1644. static overrides = {
  1645. scales: {
  1646. x: {
  1647. type: 'linear'
  1648. },
  1649. y: {
  1650. type: 'linear'
  1651. }
  1652. }
  1653. };
  1654. initialize() {
  1655. this.enableOptionSharing = true;
  1656. super.initialize();
  1657. }
  1658. parsePrimitiveData(meta, data, start, count) {
  1659. const parsed = super.parsePrimitiveData(meta, data, start, count);
  1660. for(let i = 0; i < parsed.length; i++){
  1661. parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
  1662. }
  1663. return parsed;
  1664. }
  1665. parseArrayData(meta, data, start, count) {
  1666. const parsed = super.parseArrayData(meta, data, start, count);
  1667. for(let i = 0; i < parsed.length; i++){
  1668. const item = data[start + i];
  1669. parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
  1670. }
  1671. return parsed;
  1672. }
  1673. parseObjectData(meta, data, start, count) {
  1674. const parsed = super.parseObjectData(meta, data, start, count);
  1675. for(let i = 0; i < parsed.length; i++){
  1676. const item = data[start + i];
  1677. parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
  1678. }
  1679. return parsed;
  1680. }
  1681. getMaxOverflow() {
  1682. const data = this._cachedMeta.data;
  1683. let max = 0;
  1684. for(let i = data.length - 1; i >= 0; --i){
  1685. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  1686. }
  1687. return max > 0 && max;
  1688. }
  1689. getLabelAndValue(index) {
  1690. const meta = this._cachedMeta;
  1691. const labels = this.chart.data.labels || [];
  1692. const { xScale , yScale } = meta;
  1693. const parsed = this.getParsed(index);
  1694. const x = xScale.getLabelForValue(parsed.x);
  1695. const y = yScale.getLabelForValue(parsed.y);
  1696. const r = parsed._custom;
  1697. return {
  1698. label: labels[index] || '',
  1699. value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
  1700. };
  1701. }
  1702. update(mode) {
  1703. const points = this._cachedMeta.data;
  1704. this.updateElements(points, 0, points.length, mode);
  1705. }
  1706. updateElements(points, start, count, mode) {
  1707. const reset = mode === 'reset';
  1708. const { iScale , vScale } = this._cachedMeta;
  1709. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1710. const iAxis = iScale.axis;
  1711. const vAxis = vScale.axis;
  1712. for(let i = start; i < start + count; i++){
  1713. const point = points[i];
  1714. const parsed = !reset && this.getParsed(i);
  1715. const properties = {};
  1716. const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
  1717. const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
  1718. properties.skip = isNaN(iPixel) || isNaN(vPixel);
  1719. if (includeOptions) {
  1720. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  1721. if (reset) {
  1722. properties.options.radius = 0;
  1723. }
  1724. }
  1725. this.updateElement(point, i, properties, mode);
  1726. }
  1727. }
  1728. resolveDataElementOptions(index, mode) {
  1729. const parsed = this.getParsed(index);
  1730. let values = super.resolveDataElementOptions(index, mode);
  1731. if (values.$shared) {
  1732. values = Object.assign({}, values, {
  1733. $shared: false
  1734. });
  1735. }
  1736. const radius = values.radius;
  1737. if (mode !== 'active') {
  1738. values.radius = 0;
  1739. }
  1740. values.radius += valueOrDefault(parsed && parsed._custom, radius);
  1741. return values;
  1742. }
  1743. }
  1744. function getRatioAndOffset(rotation, circumference, cutout) {
  1745. let ratioX = 1;
  1746. let ratioY = 1;
  1747. let offsetX = 0;
  1748. let offsetY = 0;
  1749. if (circumference < TAU) {
  1750. const startAngle = rotation;
  1751. const endAngle = startAngle + circumference;
  1752. const startX = Math.cos(startAngle);
  1753. const startY = Math.sin(startAngle);
  1754. const endX = Math.cos(endAngle);
  1755. const endY = Math.sin(endAngle);
  1756. const calcMax = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
  1757. const calcMin = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
  1758. const maxX = calcMax(0, startX, endX);
  1759. const maxY = calcMax(HALF_PI, startY, endY);
  1760. const minX = calcMin(PI, startX, endX);
  1761. const minY = calcMin(PI + HALF_PI, startY, endY);
  1762. ratioX = (maxX - minX) / 2;
  1763. ratioY = (maxY - minY) / 2;
  1764. offsetX = -(maxX + minX) / 2;
  1765. offsetY = -(maxY + minY) / 2;
  1766. }
  1767. return {
  1768. ratioX,
  1769. ratioY,
  1770. offsetX,
  1771. offsetY
  1772. };
  1773. }
  1774. class DoughnutController extends DatasetController {
  1775. static id = 'doughnut';
  1776. static defaults = {
  1777. datasetElementType: false,
  1778. dataElementType: 'arc',
  1779. animation: {
  1780. animateRotate: true,
  1781. animateScale: false
  1782. },
  1783. animations: {
  1784. numbers: {
  1785. type: 'number',
  1786. properties: [
  1787. 'circumference',
  1788. 'endAngle',
  1789. 'innerRadius',
  1790. 'outerRadius',
  1791. 'startAngle',
  1792. 'x',
  1793. 'y',
  1794. 'offset',
  1795. 'borderWidth',
  1796. 'spacing'
  1797. ]
  1798. }
  1799. },
  1800. cutout: '50%',
  1801. rotation: 0,
  1802. circumference: 360,
  1803. radius: '100%',
  1804. spacing: 0,
  1805. indexAxis: 'r'
  1806. };
  1807. static descriptors = {
  1808. _scriptable: (name)=>name !== 'spacing',
  1809. _indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash')
  1810. };
  1811. static overrides = {
  1812. aspectRatio: 1,
  1813. plugins: {
  1814. legend: {
  1815. labels: {
  1816. generateLabels (chart) {
  1817. const data = chart.data;
  1818. if (data.labels.length && data.datasets.length) {
  1819. const { labels: { pointStyle , color } } = chart.legend.options;
  1820. return data.labels.map((label, i)=>{
  1821. const meta = chart.getDatasetMeta(0);
  1822. const style = meta.controller.getStyle(i);
  1823. return {
  1824. text: label,
  1825. fillStyle: style.backgroundColor,
  1826. strokeStyle: style.borderColor,
  1827. fontColor: color,
  1828. lineWidth: style.borderWidth,
  1829. pointStyle: pointStyle,
  1830. hidden: !chart.getDataVisibility(i),
  1831. index: i
  1832. };
  1833. });
  1834. }
  1835. return [];
  1836. }
  1837. },
  1838. onClick (e, legendItem, legend) {
  1839. legend.chart.toggleDataVisibility(legendItem.index);
  1840. legend.chart.update();
  1841. }
  1842. }
  1843. }
  1844. };
  1845. constructor(chart, datasetIndex){
  1846. super(chart, datasetIndex);
  1847. this.enableOptionSharing = true;
  1848. this.innerRadius = undefined;
  1849. this.outerRadius = undefined;
  1850. this.offsetX = undefined;
  1851. this.offsetY = undefined;
  1852. }
  1853. linkScales() {}
  1854. parse(start, count) {
  1855. const data = this.getDataset().data;
  1856. const meta = this._cachedMeta;
  1857. if (this._parsing === false) {
  1858. meta._parsed = data;
  1859. } else {
  1860. let getter = (i)=>+data[i];
  1861. if (isObject(data[start])) {
  1862. const { key ='value' } = this._parsing;
  1863. getter = (i)=>+resolveObjectKey(data[i], key);
  1864. }
  1865. let i, ilen;
  1866. for(i = start, ilen = start + count; i < ilen; ++i){
  1867. meta._parsed[i] = getter(i);
  1868. }
  1869. }
  1870. }
  1871. _getRotation() {
  1872. return toRadians(this.options.rotation - 90);
  1873. }
  1874. _getCircumference() {
  1875. return toRadians(this.options.circumference);
  1876. }
  1877. _getRotationExtents() {
  1878. let min = TAU;
  1879. let max = -TAU;
  1880. for(let i = 0; i < this.chart.data.datasets.length; ++i){
  1881. if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
  1882. const controller = this.chart.getDatasetMeta(i).controller;
  1883. const rotation = controller._getRotation();
  1884. const circumference = controller._getCircumference();
  1885. min = Math.min(min, rotation);
  1886. max = Math.max(max, rotation + circumference);
  1887. }
  1888. }
  1889. return {
  1890. rotation: min,
  1891. circumference: max - min
  1892. };
  1893. }
  1894. update(mode) {
  1895. const chart = this.chart;
  1896. const { chartArea } = chart;
  1897. const meta = this._cachedMeta;
  1898. const arcs = meta.data;
  1899. const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
  1900. const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
  1901. const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
  1902. const chartWeight = this._getRingWeight(this.index);
  1903. const { circumference , rotation } = this._getRotationExtents();
  1904. const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
  1905. const maxWidth = (chartArea.width - spacing) / ratioX;
  1906. const maxHeight = (chartArea.height - spacing) / ratioY;
  1907. const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
  1908. const outerRadius = toDimension(this.options.radius, maxRadius);
  1909. const innerRadius = Math.max(outerRadius * cutout, 0);
  1910. const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
  1911. this.offsetX = offsetX * outerRadius;
  1912. this.offsetY = offsetY * outerRadius;
  1913. meta.total = this.calculateTotal();
  1914. this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
  1915. this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
  1916. this.updateElements(arcs, 0, arcs.length, mode);
  1917. }
  1918. _circumference(i, reset) {
  1919. const opts = this.options;
  1920. const meta = this._cachedMeta;
  1921. const circumference = this._getCircumference();
  1922. if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
  1923. return 0;
  1924. }
  1925. return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
  1926. }
  1927. updateElements(arcs, start, count, mode) {
  1928. const reset = mode === 'reset';
  1929. const chart = this.chart;
  1930. const chartArea = chart.chartArea;
  1931. const opts = chart.options;
  1932. const animationOpts = opts.animation;
  1933. const centerX = (chartArea.left + chartArea.right) / 2;
  1934. const centerY = (chartArea.top + chartArea.bottom) / 2;
  1935. const animateScale = reset && animationOpts.animateScale;
  1936. const innerRadius = animateScale ? 0 : this.innerRadius;
  1937. const outerRadius = animateScale ? 0 : this.outerRadius;
  1938. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1939. let startAngle = this._getRotation();
  1940. let i;
  1941. for(i = 0; i < start; ++i){
  1942. startAngle += this._circumference(i, reset);
  1943. }
  1944. for(i = start; i < start + count; ++i){
  1945. const circumference = this._circumference(i, reset);
  1946. const arc = arcs[i];
  1947. const properties = {
  1948. x: centerX + this.offsetX,
  1949. y: centerY + this.offsetY,
  1950. startAngle,
  1951. endAngle: startAngle + circumference,
  1952. circumference,
  1953. outerRadius,
  1954. innerRadius
  1955. };
  1956. if (includeOptions) {
  1957. properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
  1958. }
  1959. startAngle += circumference;
  1960. this.updateElement(arc, i, properties, mode);
  1961. }
  1962. }
  1963. calculateTotal() {
  1964. const meta = this._cachedMeta;
  1965. const metaData = meta.data;
  1966. let total = 0;
  1967. let i;
  1968. for(i = 0; i < metaData.length; i++){
  1969. const value = meta._parsed[i];
  1970. if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
  1971. total += Math.abs(value);
  1972. }
  1973. }
  1974. return total;
  1975. }
  1976. calculateCircumference(value) {
  1977. const total = this._cachedMeta.total;
  1978. if (total > 0 && !isNaN(value)) {
  1979. return TAU * (Math.abs(value) / total);
  1980. }
  1981. return 0;
  1982. }
  1983. getLabelAndValue(index) {
  1984. const meta = this._cachedMeta;
  1985. const chart = this.chart;
  1986. const labels = chart.data.labels || [];
  1987. const value = formatNumber(meta._parsed[index], chart.options.locale);
  1988. return {
  1989. label: labels[index] || '',
  1990. value
  1991. };
  1992. }
  1993. getMaxBorderWidth(arcs) {
  1994. let max = 0;
  1995. const chart = this.chart;
  1996. let i, ilen, meta, controller, options;
  1997. if (!arcs) {
  1998. for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
  1999. if (chart.isDatasetVisible(i)) {
  2000. meta = chart.getDatasetMeta(i);
  2001. arcs = meta.data;
  2002. controller = meta.controller;
  2003. break;
  2004. }
  2005. }
  2006. }
  2007. if (!arcs) {
  2008. return 0;
  2009. }
  2010. for(i = 0, ilen = arcs.length; i < ilen; ++i){
  2011. options = controller.resolveDataElementOptions(i);
  2012. if (options.borderAlign !== 'inner') {
  2013. max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
  2014. }
  2015. }
  2016. return max;
  2017. }
  2018. getMaxOffset(arcs) {
  2019. let max = 0;
  2020. for(let i = 0, ilen = arcs.length; i < ilen; ++i){
  2021. const options = this.resolveDataElementOptions(i);
  2022. max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
  2023. }
  2024. return max;
  2025. }
  2026. _getRingWeightOffset(datasetIndex) {
  2027. let ringWeightOffset = 0;
  2028. for(let i = 0; i < datasetIndex; ++i){
  2029. if (this.chart.isDatasetVisible(i)) {
  2030. ringWeightOffset += this._getRingWeight(i);
  2031. }
  2032. }
  2033. return ringWeightOffset;
  2034. }
  2035. _getRingWeight(datasetIndex) {
  2036. return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
  2037. }
  2038. _getVisibleDatasetWeightTotal() {
  2039. return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
  2040. }
  2041. }
  2042. class LineController extends DatasetController {
  2043. static id = 'line';
  2044. static defaults = {
  2045. datasetElementType: 'line',
  2046. dataElementType: 'point',
  2047. showLine: true,
  2048. spanGaps: false
  2049. };
  2050. static overrides = {
  2051. scales: {
  2052. _index_: {
  2053. type: 'category'
  2054. },
  2055. _value_: {
  2056. type: 'linear'
  2057. }
  2058. }
  2059. };
  2060. initialize() {
  2061. this.enableOptionSharing = true;
  2062. this.supportsDecimation = true;
  2063. super.initialize();
  2064. }
  2065. update(mode) {
  2066. const meta = this._cachedMeta;
  2067. const { dataset: line , data: points = [] , _dataset } = meta;
  2068. const animationsDisabled = this.chart._animationsDisabled;
  2069. let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2070. this._drawStart = start;
  2071. this._drawCount = count;
  2072. if (_scaleRangesChanged(meta)) {
  2073. start = 0;
  2074. count = points.length;
  2075. }
  2076. line._chart = this.chart;
  2077. line._datasetIndex = this.index;
  2078. line._decimated = !!_dataset._decimated;
  2079. line.points = points;
  2080. const options = this.resolveDatasetElementOptions(mode);
  2081. if (!this.options.showLine) {
  2082. options.borderWidth = 0;
  2083. }
  2084. options.segment = this.options.segment;
  2085. this.updateElement(line, undefined, {
  2086. animated: !animationsDisabled,
  2087. options
  2088. }, mode);
  2089. this.updateElements(points, start, count, mode);
  2090. }
  2091. updateElements(points, start, count, mode) {
  2092. const reset = mode === 'reset';
  2093. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2094. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  2095. const iAxis = iScale.axis;
  2096. const vAxis = vScale.axis;
  2097. const { spanGaps , segment } = this.options;
  2098. const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2099. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2100. const end = start + count;
  2101. const pointsCount = points.length;
  2102. let prevParsed = start > 0 && this.getParsed(start - 1);
  2103. for(let i = 0; i < pointsCount; ++i){
  2104. const point = points[i];
  2105. const properties = directUpdate ? point : {};
  2106. if (i < start || i >= end) {
  2107. properties.skip = true;
  2108. continue;
  2109. }
  2110. const parsed = this.getParsed(i);
  2111. const nullData = isNullOrUndef(parsed[vAxis]);
  2112. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2113. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2114. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2115. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2116. if (segment) {
  2117. properties.parsed = parsed;
  2118. properties.raw = _dataset.data[i];
  2119. }
  2120. if (includeOptions) {
  2121. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2122. }
  2123. if (!directUpdate) {
  2124. this.updateElement(point, i, properties, mode);
  2125. }
  2126. prevParsed = parsed;
  2127. }
  2128. }
  2129. getMaxOverflow() {
  2130. const meta = this._cachedMeta;
  2131. const dataset = meta.dataset;
  2132. const border = dataset.options && dataset.options.borderWidth || 0;
  2133. const data = meta.data || [];
  2134. if (!data.length) {
  2135. return border;
  2136. }
  2137. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2138. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2139. return Math.max(border, firstPoint, lastPoint) / 2;
  2140. }
  2141. draw() {
  2142. const meta = this._cachedMeta;
  2143. meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
  2144. super.draw();
  2145. }
  2146. }
  2147. class PolarAreaController extends DatasetController {
  2148. static id = 'polarArea';
  2149. static defaults = {
  2150. dataElementType: 'arc',
  2151. animation: {
  2152. animateRotate: true,
  2153. animateScale: true
  2154. },
  2155. animations: {
  2156. numbers: {
  2157. type: 'number',
  2158. properties: [
  2159. 'x',
  2160. 'y',
  2161. 'startAngle',
  2162. 'endAngle',
  2163. 'innerRadius',
  2164. 'outerRadius'
  2165. ]
  2166. }
  2167. },
  2168. indexAxis: 'r',
  2169. startAngle: 0
  2170. };
  2171. static overrides = {
  2172. aspectRatio: 1,
  2173. plugins: {
  2174. legend: {
  2175. labels: {
  2176. generateLabels (chart) {
  2177. const data = chart.data;
  2178. if (data.labels.length && data.datasets.length) {
  2179. const { labels: { pointStyle , color } } = chart.legend.options;
  2180. return data.labels.map((label, i)=>{
  2181. const meta = chart.getDatasetMeta(0);
  2182. const style = meta.controller.getStyle(i);
  2183. return {
  2184. text: label,
  2185. fillStyle: style.backgroundColor,
  2186. strokeStyle: style.borderColor,
  2187. fontColor: color,
  2188. lineWidth: style.borderWidth,
  2189. pointStyle: pointStyle,
  2190. hidden: !chart.getDataVisibility(i),
  2191. index: i
  2192. };
  2193. });
  2194. }
  2195. return [];
  2196. }
  2197. },
  2198. onClick (e, legendItem, legend) {
  2199. legend.chart.toggleDataVisibility(legendItem.index);
  2200. legend.chart.update();
  2201. }
  2202. }
  2203. },
  2204. scales: {
  2205. r: {
  2206. type: 'radialLinear',
  2207. angleLines: {
  2208. display: false
  2209. },
  2210. beginAtZero: true,
  2211. grid: {
  2212. circular: true
  2213. },
  2214. pointLabels: {
  2215. display: false
  2216. },
  2217. startAngle: 0
  2218. }
  2219. }
  2220. };
  2221. constructor(chart, datasetIndex){
  2222. super(chart, datasetIndex);
  2223. this.innerRadius = undefined;
  2224. this.outerRadius = undefined;
  2225. }
  2226. getLabelAndValue(index) {
  2227. const meta = this._cachedMeta;
  2228. const chart = this.chart;
  2229. const labels = chart.data.labels || [];
  2230. const value = formatNumber(meta._parsed[index].r, chart.options.locale);
  2231. return {
  2232. label: labels[index] || '',
  2233. value
  2234. };
  2235. }
  2236. parseObjectData(meta, data, start, count) {
  2237. return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2238. }
  2239. update(mode) {
  2240. const arcs = this._cachedMeta.data;
  2241. this._updateRadius();
  2242. this.updateElements(arcs, 0, arcs.length, mode);
  2243. }
  2244. getMinMax() {
  2245. const meta = this._cachedMeta;
  2246. const range = {
  2247. min: Number.POSITIVE_INFINITY,
  2248. max: Number.NEGATIVE_INFINITY
  2249. };
  2250. meta.data.forEach((element, index)=>{
  2251. const parsed = this.getParsed(index).r;
  2252. if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
  2253. if (parsed < range.min) {
  2254. range.min = parsed;
  2255. }
  2256. if (parsed > range.max) {
  2257. range.max = parsed;
  2258. }
  2259. }
  2260. });
  2261. return range;
  2262. }
  2263. _updateRadius() {
  2264. const chart = this.chart;
  2265. const chartArea = chart.chartArea;
  2266. const opts = chart.options;
  2267. const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  2268. const outerRadius = Math.max(minSize / 2, 0);
  2269. const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
  2270. const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
  2271. this.outerRadius = outerRadius - radiusLength * this.index;
  2272. this.innerRadius = this.outerRadius - radiusLength;
  2273. }
  2274. updateElements(arcs, start, count, mode) {
  2275. const reset = mode === 'reset';
  2276. const chart = this.chart;
  2277. const opts = chart.options;
  2278. const animationOpts = opts.animation;
  2279. const scale = this._cachedMeta.rScale;
  2280. const centerX = scale.xCenter;
  2281. const centerY = scale.yCenter;
  2282. const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
  2283. let angle = datasetStartAngle;
  2284. let i;
  2285. const defaultAngle = 360 / this.countVisibleElements();
  2286. for(i = 0; i < start; ++i){
  2287. angle += this._computeAngle(i, mode, defaultAngle);
  2288. }
  2289. for(i = start; i < start + count; i++){
  2290. const arc = arcs[i];
  2291. let startAngle = angle;
  2292. let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
  2293. let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
  2294. angle = endAngle;
  2295. if (reset) {
  2296. if (animationOpts.animateScale) {
  2297. outerRadius = 0;
  2298. }
  2299. if (animationOpts.animateRotate) {
  2300. startAngle = endAngle = datasetStartAngle;
  2301. }
  2302. }
  2303. const properties = {
  2304. x: centerX,
  2305. y: centerY,
  2306. innerRadius: 0,
  2307. outerRadius,
  2308. startAngle,
  2309. endAngle,
  2310. options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
  2311. };
  2312. this.updateElement(arc, i, properties, mode);
  2313. }
  2314. }
  2315. countVisibleElements() {
  2316. const meta = this._cachedMeta;
  2317. let count = 0;
  2318. meta.data.forEach((element, index)=>{
  2319. if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
  2320. count++;
  2321. }
  2322. });
  2323. return count;
  2324. }
  2325. _computeAngle(index, mode, defaultAngle) {
  2326. return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
  2327. }
  2328. }
  2329. class PieController extends DoughnutController {
  2330. static id = 'pie';
  2331. static defaults = {
  2332. cutout: 0,
  2333. rotation: 0,
  2334. circumference: 360,
  2335. radius: '100%'
  2336. };
  2337. }
  2338. class RadarController extends DatasetController {
  2339. static id = 'radar';
  2340. static defaults = {
  2341. datasetElementType: 'line',
  2342. dataElementType: 'point',
  2343. indexAxis: 'r',
  2344. showLine: true,
  2345. elements: {
  2346. line: {
  2347. fill: 'start'
  2348. }
  2349. }
  2350. };
  2351. static overrides = {
  2352. aspectRatio: 1,
  2353. scales: {
  2354. r: {
  2355. type: 'radialLinear'
  2356. }
  2357. }
  2358. };
  2359. getLabelAndValue(index) {
  2360. const vScale = this._cachedMeta.vScale;
  2361. const parsed = this.getParsed(index);
  2362. return {
  2363. label: vScale.getLabels()[index],
  2364. value: '' + vScale.getLabelForValue(parsed[vScale.axis])
  2365. };
  2366. }
  2367. parseObjectData(meta, data, start, count) {
  2368. return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2369. }
  2370. update(mode) {
  2371. const meta = this._cachedMeta;
  2372. const line = meta.dataset;
  2373. const points = meta.data || [];
  2374. const labels = meta.iScale.getLabels();
  2375. line.points = points;
  2376. if (mode !== 'resize') {
  2377. const options = this.resolveDatasetElementOptions(mode);
  2378. if (!this.options.showLine) {
  2379. options.borderWidth = 0;
  2380. }
  2381. const properties = {
  2382. _loop: true,
  2383. _fullLoop: labels.length === points.length,
  2384. options
  2385. };
  2386. this.updateElement(line, undefined, properties, mode);
  2387. }
  2388. this.updateElements(points, 0, points.length, mode);
  2389. }
  2390. updateElements(points, start, count, mode) {
  2391. const scale = this._cachedMeta.rScale;
  2392. const reset = mode === 'reset';
  2393. for(let i = start; i < start + count; i++){
  2394. const point = points[i];
  2395. const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2396. const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
  2397. const x = reset ? scale.xCenter : pointPosition.x;
  2398. const y = reset ? scale.yCenter : pointPosition.y;
  2399. const properties = {
  2400. x,
  2401. y,
  2402. angle: pointPosition.angle,
  2403. skip: isNaN(x) || isNaN(y),
  2404. options
  2405. };
  2406. this.updateElement(point, i, properties, mode);
  2407. }
  2408. }
  2409. }
  2410. class ScatterController extends DatasetController {
  2411. static id = 'scatter';
  2412. static defaults = {
  2413. datasetElementType: false,
  2414. dataElementType: 'point',
  2415. showLine: false,
  2416. fill: false
  2417. };
  2418. static overrides = {
  2419. interaction: {
  2420. mode: 'point'
  2421. },
  2422. scales: {
  2423. x: {
  2424. type: 'linear'
  2425. },
  2426. y: {
  2427. type: 'linear'
  2428. }
  2429. }
  2430. };
  2431. getLabelAndValue(index) {
  2432. const meta = this._cachedMeta;
  2433. const labels = this.chart.data.labels || [];
  2434. const { xScale , yScale } = meta;
  2435. const parsed = this.getParsed(index);
  2436. const x = xScale.getLabelForValue(parsed.x);
  2437. const y = yScale.getLabelForValue(parsed.y);
  2438. return {
  2439. label: labels[index] || '',
  2440. value: '(' + x + ', ' + y + ')'
  2441. };
  2442. }
  2443. update(mode) {
  2444. const meta = this._cachedMeta;
  2445. const { data: points = [] } = meta;
  2446. const animationsDisabled = this.chart._animationsDisabled;
  2447. let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2448. this._drawStart = start;
  2449. this._drawCount = count;
  2450. if (_scaleRangesChanged(meta)) {
  2451. start = 0;
  2452. count = points.length;
  2453. }
  2454. if (this.options.showLine) {
  2455. if (!this.datasetElementType) {
  2456. this.addElements();
  2457. }
  2458. const { dataset: line , _dataset } = meta;
  2459. line._chart = this.chart;
  2460. line._datasetIndex = this.index;
  2461. line._decimated = !!_dataset._decimated;
  2462. line.points = points;
  2463. const options = this.resolveDatasetElementOptions(mode);
  2464. options.segment = this.options.segment;
  2465. this.updateElement(line, undefined, {
  2466. animated: !animationsDisabled,
  2467. options
  2468. }, mode);
  2469. } else if (this.datasetElementType) {
  2470. delete meta.dataset;
  2471. this.datasetElementType = false;
  2472. }
  2473. this.updateElements(points, start, count, mode);
  2474. }
  2475. addElements() {
  2476. const { showLine } = this.options;
  2477. if (!this.datasetElementType && showLine) {
  2478. this.datasetElementType = this.chart.registry.getElement('line');
  2479. }
  2480. super.addElements();
  2481. }
  2482. updateElements(points, start, count, mode) {
  2483. const reset = mode === 'reset';
  2484. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2485. const firstOpts = this.resolveDataElementOptions(start, mode);
  2486. const sharedOptions = this.getSharedOptions(firstOpts);
  2487. const includeOptions = this.includeOptions(mode, sharedOptions);
  2488. const iAxis = iScale.axis;
  2489. const vAxis = vScale.axis;
  2490. const { spanGaps , segment } = this.options;
  2491. const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2492. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2493. let prevParsed = start > 0 && this.getParsed(start - 1);
  2494. for(let i = start; i < start + count; ++i){
  2495. const point = points[i];
  2496. const parsed = this.getParsed(i);
  2497. const properties = directUpdate ? point : {};
  2498. const nullData = isNullOrUndef(parsed[vAxis]);
  2499. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2500. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2501. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2502. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2503. if (segment) {
  2504. properties.parsed = parsed;
  2505. properties.raw = _dataset.data[i];
  2506. }
  2507. if (includeOptions) {
  2508. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2509. }
  2510. if (!directUpdate) {
  2511. this.updateElement(point, i, properties, mode);
  2512. }
  2513. prevParsed = parsed;
  2514. }
  2515. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  2516. }
  2517. getMaxOverflow() {
  2518. const meta = this._cachedMeta;
  2519. const data = meta.data || [];
  2520. if (!this.options.showLine) {
  2521. let max = 0;
  2522. for(let i = data.length - 1; i >= 0; --i){
  2523. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  2524. }
  2525. return max > 0 && max;
  2526. }
  2527. const dataset = meta.dataset;
  2528. const border = dataset.options && dataset.options.borderWidth || 0;
  2529. if (!data.length) {
  2530. return border;
  2531. }
  2532. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2533. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2534. return Math.max(border, firstPoint, lastPoint) / 2;
  2535. }
  2536. }
  2537. var controllers = /*#__PURE__*/Object.freeze({
  2538. __proto__: null,
  2539. BarController: BarController,
  2540. BubbleController: BubbleController,
  2541. DoughnutController: DoughnutController,
  2542. LineController: LineController,
  2543. PieController: PieController,
  2544. PolarAreaController: PolarAreaController,
  2545. RadarController: RadarController,
  2546. ScatterController: ScatterController
  2547. });
  2548. /**
  2549. * @namespace Chart._adapters
  2550. * @since 2.8.0
  2551. * @private
  2552. */ function abstract() {
  2553. throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  2554. }
  2555. /**
  2556. * Date adapter (current used by the time scale)
  2557. * @namespace Chart._adapters._date
  2558. * @memberof Chart._adapters
  2559. * @private
  2560. */ class DateAdapterBase {
  2561. /**
  2562. * Override default date adapter methods.
  2563. * Accepts type parameter to define options type.
  2564. * @example
  2565. * Chart._adapters._date.override<{myAdapterOption: string}>({
  2566. * init() {
  2567. * console.log(this.options.myAdapterOption);
  2568. * }
  2569. * })
  2570. */ static override(members) {
  2571. Object.assign(DateAdapterBase.prototype, members);
  2572. }
  2573. options;
  2574. constructor(options){
  2575. this.options = options || {};
  2576. }
  2577. // eslint-disable-next-line @typescript-eslint/no-empty-function
  2578. init() {}
  2579. formats() {
  2580. return abstract();
  2581. }
  2582. parse() {
  2583. return abstract();
  2584. }
  2585. format() {
  2586. return abstract();
  2587. }
  2588. add() {
  2589. return abstract();
  2590. }
  2591. diff() {
  2592. return abstract();
  2593. }
  2594. startOf() {
  2595. return abstract();
  2596. }
  2597. endOf() {
  2598. return abstract();
  2599. }
  2600. }
  2601. var adapters = {
  2602. _date: DateAdapterBase
  2603. };
  2604. function binarySearch(metaset, axis, value, intersect) {
  2605. const { controller , data , _sorted } = metaset;
  2606. const iScale = controller._cachedMeta.iScale;
  2607. if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
  2608. const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
  2609. if (!intersect) {
  2610. return lookupMethod(data, axis, value);
  2611. } else if (controller._sharedOptions) {
  2612. const el = data[0];
  2613. const range = typeof el.getRange === 'function' && el.getRange(axis);
  2614. if (range) {
  2615. const start = lookupMethod(data, axis, value - range);
  2616. const end = lookupMethod(data, axis, value + range);
  2617. return {
  2618. lo: start.lo,
  2619. hi: end.hi
  2620. };
  2621. }
  2622. }
  2623. }
  2624. return {
  2625. lo: 0,
  2626. hi: data.length - 1
  2627. };
  2628. }
  2629. function evaluateInteractionItems(chart, axis, position, handler, intersect) {
  2630. const metasets = chart.getSortedVisibleDatasetMetas();
  2631. const value = position[axis];
  2632. for(let i = 0, ilen = metasets.length; i < ilen; ++i){
  2633. const { index , data } = metasets[i];
  2634. const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
  2635. for(let j = lo; j <= hi; ++j){
  2636. const element = data[j];
  2637. if (!element.skip) {
  2638. handler(element, index, j);
  2639. }
  2640. }
  2641. }
  2642. }
  2643. function getDistanceMetricForAxis(axis) {
  2644. const useX = axis.indexOf('x') !== -1;
  2645. const useY = axis.indexOf('y') !== -1;
  2646. return function(pt1, pt2) {
  2647. const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  2648. const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  2649. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  2650. };
  2651. }
  2652. function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
  2653. const items = [];
  2654. if (!includeInvisible && !chart.isPointInArea(position)) {
  2655. return items;
  2656. }
  2657. const evaluationFunc = function(element, datasetIndex, index) {
  2658. if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
  2659. return;
  2660. }
  2661. if (element.inRange(position.x, position.y, useFinalPosition)) {
  2662. items.push({
  2663. element,
  2664. datasetIndex,
  2665. index
  2666. });
  2667. }
  2668. };
  2669. evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
  2670. return items;
  2671. }
  2672. function getNearestRadialItems(chart, position, axis, useFinalPosition) {
  2673. let items = [];
  2674. function evaluationFunc(element, datasetIndex, index) {
  2675. const { startAngle , endAngle } = element.getProps([
  2676. 'startAngle',
  2677. 'endAngle'
  2678. ], useFinalPosition);
  2679. const { angle } = getAngleFromPoint(element, {
  2680. x: position.x,
  2681. y: position.y
  2682. });
  2683. if (_angleBetween(angle, startAngle, endAngle)) {
  2684. items.push({
  2685. element,
  2686. datasetIndex,
  2687. index
  2688. });
  2689. }
  2690. }
  2691. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2692. return items;
  2693. }
  2694. function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2695. let items = [];
  2696. const distanceMetric = getDistanceMetricForAxis(axis);
  2697. let minDistance = Number.POSITIVE_INFINITY;
  2698. function evaluationFunc(element, datasetIndex, index) {
  2699. const inRange = element.inRange(position.x, position.y, useFinalPosition);
  2700. if (intersect && !inRange) {
  2701. return;
  2702. }
  2703. const center = element.getCenterPoint(useFinalPosition);
  2704. const pointInArea = !!includeInvisible || chart.isPointInArea(center);
  2705. if (!pointInArea && !inRange) {
  2706. return;
  2707. }
  2708. const distance = distanceMetric(position, center);
  2709. if (distance < minDistance) {
  2710. items = [
  2711. {
  2712. element,
  2713. datasetIndex,
  2714. index
  2715. }
  2716. ];
  2717. minDistance = distance;
  2718. } else if (distance === minDistance) {
  2719. items.push({
  2720. element,
  2721. datasetIndex,
  2722. index
  2723. });
  2724. }
  2725. }
  2726. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2727. return items;
  2728. }
  2729. function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2730. if (!includeInvisible && !chart.isPointInArea(position)) {
  2731. return [];
  2732. }
  2733. return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
  2734. }
  2735. function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
  2736. const items = [];
  2737. const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
  2738. let intersectsItem = false;
  2739. evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
  2740. if (element[rangeMethod](position[axis], useFinalPosition)) {
  2741. items.push({
  2742. element,
  2743. datasetIndex,
  2744. index
  2745. });
  2746. intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
  2747. }
  2748. });
  2749. if (intersect && !intersectsItem) {
  2750. return [];
  2751. }
  2752. return items;
  2753. }
  2754. var Interaction = {
  2755. evaluateInteractionItems,
  2756. modes: {
  2757. index (chart, e, options, useFinalPosition) {
  2758. const position = getRelativePosition(e, chart);
  2759. const axis = options.axis || 'x';
  2760. const includeInvisible = options.includeInvisible || false;
  2761. const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2762. const elements = [];
  2763. if (!items.length) {
  2764. return [];
  2765. }
  2766. chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
  2767. const index = items[0].index;
  2768. const element = meta.data[index];
  2769. if (element && !element.skip) {
  2770. elements.push({
  2771. element,
  2772. datasetIndex: meta.index,
  2773. index
  2774. });
  2775. }
  2776. });
  2777. return elements;
  2778. },
  2779. dataset (chart, e, options, useFinalPosition) {
  2780. const position = getRelativePosition(e, chart);
  2781. const axis = options.axis || 'xy';
  2782. const includeInvisible = options.includeInvisible || false;
  2783. let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2784. if (items.length > 0) {
  2785. const datasetIndex = items[0].datasetIndex;
  2786. const data = chart.getDatasetMeta(datasetIndex).data;
  2787. items = [];
  2788. for(let i = 0; i < data.length; ++i){
  2789. items.push({
  2790. element: data[i],
  2791. datasetIndex,
  2792. index: i
  2793. });
  2794. }
  2795. }
  2796. return items;
  2797. },
  2798. point (chart, e, options, useFinalPosition) {
  2799. const position = getRelativePosition(e, chart);
  2800. const axis = options.axis || 'xy';
  2801. const includeInvisible = options.includeInvisible || false;
  2802. return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
  2803. },
  2804. nearest (chart, e, options, useFinalPosition) {
  2805. const position = getRelativePosition(e, chart);
  2806. const axis = options.axis || 'xy';
  2807. const includeInvisible = options.includeInvisible || false;
  2808. return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
  2809. },
  2810. x (chart, e, options, useFinalPosition) {
  2811. const position = getRelativePosition(e, chart);
  2812. return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
  2813. },
  2814. y (chart, e, options, useFinalPosition) {
  2815. const position = getRelativePosition(e, chart);
  2816. return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
  2817. }
  2818. }
  2819. };
  2820. const STATIC_POSITIONS = [
  2821. 'left',
  2822. 'top',
  2823. 'right',
  2824. 'bottom'
  2825. ];
  2826. function filterByPosition(array, position) {
  2827. return array.filter((v)=>v.pos === position);
  2828. }
  2829. function filterDynamicPositionByAxis(array, axis) {
  2830. return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
  2831. }
  2832. function sortByWeight(array, reverse) {
  2833. return array.sort((a, b)=>{
  2834. const v0 = reverse ? b : a;
  2835. const v1 = reverse ? a : b;
  2836. return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
  2837. });
  2838. }
  2839. function wrapBoxes(boxes) {
  2840. const layoutBoxes = [];
  2841. let i, ilen, box, pos, stack, stackWeight;
  2842. for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
  2843. box = boxes[i];
  2844. ({ position: pos , options: { stack , stackWeight =1 } } = box);
  2845. layoutBoxes.push({
  2846. index: i,
  2847. box,
  2848. pos,
  2849. horizontal: box.isHorizontal(),
  2850. weight: box.weight,
  2851. stack: stack && pos + stack,
  2852. stackWeight
  2853. });
  2854. }
  2855. return layoutBoxes;
  2856. }
  2857. function buildStacks(layouts) {
  2858. const stacks = {};
  2859. for (const wrap of layouts){
  2860. const { stack , pos , stackWeight } = wrap;
  2861. if (!stack || !STATIC_POSITIONS.includes(pos)) {
  2862. continue;
  2863. }
  2864. const _stack = stacks[stack] || (stacks[stack] = {
  2865. count: 0,
  2866. placed: 0,
  2867. weight: 0,
  2868. size: 0
  2869. });
  2870. _stack.count++;
  2871. _stack.weight += stackWeight;
  2872. }
  2873. return stacks;
  2874. }
  2875. function setLayoutDims(layouts, params) {
  2876. const stacks = buildStacks(layouts);
  2877. const { vBoxMaxWidth , hBoxMaxHeight } = params;
  2878. let i, ilen, layout;
  2879. for(i = 0, ilen = layouts.length; i < ilen; ++i){
  2880. layout = layouts[i];
  2881. const { fullSize } = layout.box;
  2882. const stack = stacks[layout.stack];
  2883. const factor = stack && layout.stackWeight / stack.weight;
  2884. if (layout.horizontal) {
  2885. layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
  2886. layout.height = hBoxMaxHeight;
  2887. } else {
  2888. layout.width = vBoxMaxWidth;
  2889. layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
  2890. }
  2891. }
  2892. return stacks;
  2893. }
  2894. function buildLayoutBoxes(boxes) {
  2895. const layoutBoxes = wrapBoxes(boxes);
  2896. const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
  2897. const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
  2898. const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
  2899. const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
  2900. const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
  2901. const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
  2902. const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
  2903. return {
  2904. fullSize,
  2905. leftAndTop: left.concat(top),
  2906. rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
  2907. chartArea: filterByPosition(layoutBoxes, 'chartArea'),
  2908. vertical: left.concat(right).concat(centerVertical),
  2909. horizontal: top.concat(bottom).concat(centerHorizontal)
  2910. };
  2911. }
  2912. function getCombinedMax(maxPadding, chartArea, a, b) {
  2913. return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  2914. }
  2915. function updateMaxPadding(maxPadding, boxPadding) {
  2916. maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
  2917. maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
  2918. maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
  2919. maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  2920. }
  2921. function updateDims(chartArea, params, layout, stacks) {
  2922. const { pos , box } = layout;
  2923. const maxPadding = chartArea.maxPadding;
  2924. if (!isObject(pos)) {
  2925. if (layout.size) {
  2926. chartArea[pos] -= layout.size;
  2927. }
  2928. const stack = stacks[layout.stack] || {
  2929. size: 0,
  2930. count: 1
  2931. };
  2932. stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
  2933. layout.size = stack.size / stack.count;
  2934. chartArea[pos] += layout.size;
  2935. }
  2936. if (box.getPadding) {
  2937. updateMaxPadding(maxPadding, box.getPadding());
  2938. }
  2939. const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
  2940. const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
  2941. const widthChanged = newWidth !== chartArea.w;
  2942. const heightChanged = newHeight !== chartArea.h;
  2943. chartArea.w = newWidth;
  2944. chartArea.h = newHeight;
  2945. return layout.horizontal ? {
  2946. same: widthChanged,
  2947. other: heightChanged
  2948. } : {
  2949. same: heightChanged,
  2950. other: widthChanged
  2951. };
  2952. }
  2953. function handleMaxPadding(chartArea) {
  2954. const maxPadding = chartArea.maxPadding;
  2955. function updatePos(pos) {
  2956. const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
  2957. chartArea[pos] += change;
  2958. return change;
  2959. }
  2960. chartArea.y += updatePos('top');
  2961. chartArea.x += updatePos('left');
  2962. updatePos('right');
  2963. updatePos('bottom');
  2964. }
  2965. function getMargins(horizontal, chartArea) {
  2966. const maxPadding = chartArea.maxPadding;
  2967. function marginForPositions(positions) {
  2968. const margin = {
  2969. left: 0,
  2970. top: 0,
  2971. right: 0,
  2972. bottom: 0
  2973. };
  2974. positions.forEach((pos)=>{
  2975. margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
  2976. });
  2977. return margin;
  2978. }
  2979. return horizontal ? marginForPositions([
  2980. 'left',
  2981. 'right'
  2982. ]) : marginForPositions([
  2983. 'top',
  2984. 'bottom'
  2985. ]);
  2986. }
  2987. function fitBoxes(boxes, chartArea, params, stacks) {
  2988. const refitBoxes = [];
  2989. let i, ilen, layout, box, refit, changed;
  2990. for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
  2991. layout = boxes[i];
  2992. box = layout.box;
  2993. box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
  2994. const { same , other } = updateDims(chartArea, params, layout, stacks);
  2995. refit |= same && refitBoxes.length;
  2996. changed = changed || other;
  2997. if (!box.fullSize) {
  2998. refitBoxes.push(layout);
  2999. }
  3000. }
  3001. return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  3002. }
  3003. function setBoxDims(box, left, top, width, height) {
  3004. box.top = top;
  3005. box.left = left;
  3006. box.right = left + width;
  3007. box.bottom = top + height;
  3008. box.width = width;
  3009. box.height = height;
  3010. }
  3011. function placeBoxes(boxes, chartArea, params, stacks) {
  3012. const userPadding = params.padding;
  3013. let { x , y } = chartArea;
  3014. for (const layout of boxes){
  3015. const box = layout.box;
  3016. const stack = stacks[layout.stack] || {
  3017. count: 1,
  3018. placed: 0,
  3019. weight: 1
  3020. };
  3021. const weight = layout.stackWeight / stack.weight || 1;
  3022. if (layout.horizontal) {
  3023. const width = chartArea.w * weight;
  3024. const height = stack.size || box.height;
  3025. if (defined(stack.start)) {
  3026. y = stack.start;
  3027. }
  3028. if (box.fullSize) {
  3029. setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
  3030. } else {
  3031. setBoxDims(box, chartArea.left + stack.placed, y, width, height);
  3032. }
  3033. stack.start = y;
  3034. stack.placed += width;
  3035. y = box.bottom;
  3036. } else {
  3037. const height = chartArea.h * weight;
  3038. const width = stack.size || box.width;
  3039. if (defined(stack.start)) {
  3040. x = stack.start;
  3041. }
  3042. if (box.fullSize) {
  3043. setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
  3044. } else {
  3045. setBoxDims(box, x, chartArea.top + stack.placed, width, height);
  3046. }
  3047. stack.start = x;
  3048. stack.placed += height;
  3049. x = box.right;
  3050. }
  3051. }
  3052. chartArea.x = x;
  3053. chartArea.y = y;
  3054. }
  3055. var layouts = {
  3056. addBox (chart, item) {
  3057. if (!chart.boxes) {
  3058. chart.boxes = [];
  3059. }
  3060. item.fullSize = item.fullSize || false;
  3061. item.position = item.position || 'top';
  3062. item.weight = item.weight || 0;
  3063. item._layers = item._layers || function() {
  3064. return [
  3065. {
  3066. z: 0,
  3067. draw (chartArea) {
  3068. item.draw(chartArea);
  3069. }
  3070. }
  3071. ];
  3072. };
  3073. chart.boxes.push(item);
  3074. },
  3075. removeBox (chart, layoutItem) {
  3076. const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
  3077. if (index !== -1) {
  3078. chart.boxes.splice(index, 1);
  3079. }
  3080. },
  3081. configure (chart, item, options) {
  3082. item.fullSize = options.fullSize;
  3083. item.position = options.position;
  3084. item.weight = options.weight;
  3085. },
  3086. update (chart, width, height, minPadding) {
  3087. if (!chart) {
  3088. return;
  3089. }
  3090. const padding = toPadding(chart.options.layout.padding);
  3091. const availableWidth = Math.max(width - padding.width, 0);
  3092. const availableHeight = Math.max(height - padding.height, 0);
  3093. const boxes = buildLayoutBoxes(chart.boxes);
  3094. const verticalBoxes = boxes.vertical;
  3095. const horizontalBoxes = boxes.horizontal;
  3096. each(chart.boxes, (box)=>{
  3097. if (typeof box.beforeLayout === 'function') {
  3098. box.beforeLayout();
  3099. }
  3100. });
  3101. const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
  3102. const params = Object.freeze({
  3103. outerWidth: width,
  3104. outerHeight: height,
  3105. padding,
  3106. availableWidth,
  3107. availableHeight,
  3108. vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
  3109. hBoxMaxHeight: availableHeight / 2
  3110. });
  3111. const maxPadding = Object.assign({}, padding);
  3112. updateMaxPadding(maxPadding, toPadding(minPadding));
  3113. const chartArea = Object.assign({
  3114. maxPadding,
  3115. w: availableWidth,
  3116. h: availableHeight,
  3117. x: padding.left,
  3118. y: padding.top
  3119. }, padding);
  3120. const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
  3121. fitBoxes(boxes.fullSize, chartArea, params, stacks);
  3122. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3123. if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
  3124. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3125. }
  3126. handleMaxPadding(chartArea);
  3127. placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
  3128. chartArea.x += chartArea.w;
  3129. chartArea.y += chartArea.h;
  3130. placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
  3131. chart.chartArea = {
  3132. left: chartArea.left,
  3133. top: chartArea.top,
  3134. right: chartArea.left + chartArea.w,
  3135. bottom: chartArea.top + chartArea.h,
  3136. height: chartArea.h,
  3137. width: chartArea.w
  3138. };
  3139. each(boxes.chartArea, (layout)=>{
  3140. const box = layout.box;
  3141. Object.assign(box, chart.chartArea);
  3142. box.update(chartArea.w, chartArea.h, {
  3143. left: 0,
  3144. top: 0,
  3145. right: 0,
  3146. bottom: 0
  3147. });
  3148. });
  3149. }
  3150. };
  3151. class BasePlatform {
  3152. acquireContext(canvas, aspectRatio) {}
  3153. releaseContext(context) {
  3154. return false;
  3155. }
  3156. addEventListener(chart, type, listener) {}
  3157. removeEventListener(chart, type, listener) {}
  3158. getDevicePixelRatio() {
  3159. return 1;
  3160. }
  3161. getMaximumSize(element, width, height, aspectRatio) {
  3162. width = Math.max(0, width || element.width);
  3163. height = height || element.height;
  3164. return {
  3165. width,
  3166. height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
  3167. };
  3168. }
  3169. isAttached(canvas) {
  3170. return true;
  3171. }
  3172. updateConfig(config) {
  3173. }
  3174. }
  3175. class BasicPlatform extends BasePlatform {
  3176. acquireContext(item) {
  3177. return item && item.getContext && item.getContext('2d') || null;
  3178. }
  3179. updateConfig(config) {
  3180. config.options.animation = false;
  3181. }
  3182. }
  3183. const EXPANDO_KEY = '$chartjs';
  3184. const EVENT_TYPES = {
  3185. touchstart: 'mousedown',
  3186. touchmove: 'mousemove',
  3187. touchend: 'mouseup',
  3188. pointerenter: 'mouseenter',
  3189. pointerdown: 'mousedown',
  3190. pointermove: 'mousemove',
  3191. pointerup: 'mouseup',
  3192. pointerleave: 'mouseout',
  3193. pointerout: 'mouseout'
  3194. };
  3195. const isNullOrEmpty = (value)=>value === null || value === '';
  3196. function initCanvas(canvas, aspectRatio) {
  3197. const style = canvas.style;
  3198. const renderHeight = canvas.getAttribute('height');
  3199. const renderWidth = canvas.getAttribute('width');
  3200. canvas[EXPANDO_KEY] = {
  3201. initial: {
  3202. height: renderHeight,
  3203. width: renderWidth,
  3204. style: {
  3205. display: style.display,
  3206. height: style.height,
  3207. width: style.width
  3208. }
  3209. }
  3210. };
  3211. style.display = style.display || 'block';
  3212. style.boxSizing = style.boxSizing || 'border-box';
  3213. if (isNullOrEmpty(renderWidth)) {
  3214. const displayWidth = readUsedSize(canvas, 'width');
  3215. if (displayWidth !== undefined) {
  3216. canvas.width = displayWidth;
  3217. }
  3218. }
  3219. if (isNullOrEmpty(renderHeight)) {
  3220. if (canvas.style.height === '') {
  3221. canvas.height = canvas.width / (aspectRatio || 2);
  3222. } else {
  3223. const displayHeight = readUsedSize(canvas, 'height');
  3224. if (displayHeight !== undefined) {
  3225. canvas.height = displayHeight;
  3226. }
  3227. }
  3228. }
  3229. return canvas;
  3230. }
  3231. const eventListenerOptions = supportsEventListenerOptions ? {
  3232. passive: true
  3233. } : false;
  3234. function addListener(node, type, listener) {
  3235. node.addEventListener(type, listener, eventListenerOptions);
  3236. }
  3237. function removeListener(chart, type, listener) {
  3238. chart.canvas.removeEventListener(type, listener, eventListenerOptions);
  3239. }
  3240. function fromNativeEvent(event, chart) {
  3241. const type = EVENT_TYPES[event.type] || event.type;
  3242. const { x , y } = getRelativePosition(event, chart);
  3243. return {
  3244. type,
  3245. chart,
  3246. native: event,
  3247. x: x !== undefined ? x : null,
  3248. y: y !== undefined ? y : null
  3249. };
  3250. }
  3251. function nodeListContains(nodeList, canvas) {
  3252. for (const node of nodeList){
  3253. if (node === canvas || node.contains(canvas)) {
  3254. return true;
  3255. }
  3256. }
  3257. }
  3258. function createAttachObserver(chart, type, listener) {
  3259. const canvas = chart.canvas;
  3260. const observer = new MutationObserver((entries)=>{
  3261. let trigger = false;
  3262. for (const entry of entries){
  3263. trigger = trigger || nodeListContains(entry.addedNodes, canvas);
  3264. trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
  3265. }
  3266. if (trigger) {
  3267. listener();
  3268. }
  3269. });
  3270. observer.observe(document, {
  3271. childList: true,
  3272. subtree: true
  3273. });
  3274. return observer;
  3275. }
  3276. function createDetachObserver(chart, type, listener) {
  3277. const canvas = chart.canvas;
  3278. const observer = new MutationObserver((entries)=>{
  3279. let trigger = false;
  3280. for (const entry of entries){
  3281. trigger = trigger || nodeListContains(entry.removedNodes, canvas);
  3282. trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
  3283. }
  3284. if (trigger) {
  3285. listener();
  3286. }
  3287. });
  3288. observer.observe(document, {
  3289. childList: true,
  3290. subtree: true
  3291. });
  3292. return observer;
  3293. }
  3294. const drpListeningCharts = new Map();
  3295. let oldDevicePixelRatio = 0;
  3296. function onWindowResize() {
  3297. const dpr = window.devicePixelRatio;
  3298. if (dpr === oldDevicePixelRatio) {
  3299. return;
  3300. }
  3301. oldDevicePixelRatio = dpr;
  3302. drpListeningCharts.forEach((resize, chart)=>{
  3303. if (chart.currentDevicePixelRatio !== dpr) {
  3304. resize();
  3305. }
  3306. });
  3307. }
  3308. function listenDevicePixelRatioChanges(chart, resize) {
  3309. if (!drpListeningCharts.size) {
  3310. window.addEventListener('resize', onWindowResize);
  3311. }
  3312. drpListeningCharts.set(chart, resize);
  3313. }
  3314. function unlistenDevicePixelRatioChanges(chart) {
  3315. drpListeningCharts.delete(chart);
  3316. if (!drpListeningCharts.size) {
  3317. window.removeEventListener('resize', onWindowResize);
  3318. }
  3319. }
  3320. function createResizeObserver(chart, type, listener) {
  3321. const canvas = chart.canvas;
  3322. const container = canvas && _getParentNode(canvas);
  3323. if (!container) {
  3324. return;
  3325. }
  3326. const resize = throttled((width, height)=>{
  3327. const w = container.clientWidth;
  3328. listener(width, height);
  3329. if (w < container.clientWidth) {
  3330. listener();
  3331. }
  3332. }, window);
  3333. const observer = new ResizeObserver((entries)=>{
  3334. const entry = entries[0];
  3335. const width = entry.contentRect.width;
  3336. const height = entry.contentRect.height;
  3337. if (width === 0 && height === 0) {
  3338. return;
  3339. }
  3340. resize(width, height);
  3341. });
  3342. observer.observe(container);
  3343. listenDevicePixelRatioChanges(chart, resize);
  3344. return observer;
  3345. }
  3346. function releaseObserver(chart, type, observer) {
  3347. if (observer) {
  3348. observer.disconnect();
  3349. }
  3350. if (type === 'resize') {
  3351. unlistenDevicePixelRatioChanges(chart);
  3352. }
  3353. }
  3354. function createProxyAndListen(chart, type, listener) {
  3355. const canvas = chart.canvas;
  3356. const proxy = throttled((event)=>{
  3357. if (chart.ctx !== null) {
  3358. listener(fromNativeEvent(event, chart));
  3359. }
  3360. }, chart);
  3361. addListener(canvas, type, proxy);
  3362. return proxy;
  3363. }
  3364. class DomPlatform extends BasePlatform {
  3365. acquireContext(canvas, aspectRatio) {
  3366. const context = canvas && canvas.getContext && canvas.getContext('2d');
  3367. if (context && context.canvas === canvas) {
  3368. initCanvas(canvas, aspectRatio);
  3369. return context;
  3370. }
  3371. return null;
  3372. }
  3373. releaseContext(context) {
  3374. const canvas = context.canvas;
  3375. if (!canvas[EXPANDO_KEY]) {
  3376. return false;
  3377. }
  3378. const initial = canvas[EXPANDO_KEY].initial;
  3379. [
  3380. 'height',
  3381. 'width'
  3382. ].forEach((prop)=>{
  3383. const value = initial[prop];
  3384. if (isNullOrUndef(value)) {
  3385. canvas.removeAttribute(prop);
  3386. } else {
  3387. canvas.setAttribute(prop, value);
  3388. }
  3389. });
  3390. const style = initial.style || {};
  3391. Object.keys(style).forEach((key)=>{
  3392. canvas.style[key] = style[key];
  3393. });
  3394. canvas.width = canvas.width;
  3395. delete canvas[EXPANDO_KEY];
  3396. return true;
  3397. }
  3398. addEventListener(chart, type, listener) {
  3399. this.removeEventListener(chart, type);
  3400. const proxies = chart.$proxies || (chart.$proxies = {});
  3401. const handlers = {
  3402. attach: createAttachObserver,
  3403. detach: createDetachObserver,
  3404. resize: createResizeObserver
  3405. };
  3406. const handler = handlers[type] || createProxyAndListen;
  3407. proxies[type] = handler(chart, type, listener);
  3408. }
  3409. removeEventListener(chart, type) {
  3410. const proxies = chart.$proxies || (chart.$proxies = {});
  3411. const proxy = proxies[type];
  3412. if (!proxy) {
  3413. return;
  3414. }
  3415. const handlers = {
  3416. attach: releaseObserver,
  3417. detach: releaseObserver,
  3418. resize: releaseObserver
  3419. };
  3420. const handler = handlers[type] || removeListener;
  3421. handler(chart, type, proxy);
  3422. proxies[type] = undefined;
  3423. }
  3424. getDevicePixelRatio() {
  3425. return window.devicePixelRatio;
  3426. }
  3427. getMaximumSize(canvas, width, height, aspectRatio) {
  3428. return getMaximumSize(canvas, width, height, aspectRatio);
  3429. }
  3430. isAttached(canvas) {
  3431. const container = _getParentNode(canvas);
  3432. return !!(container && container.isConnected);
  3433. }
  3434. }
  3435. function _detectPlatform(canvas) {
  3436. if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
  3437. return BasicPlatform;
  3438. }
  3439. return DomPlatform;
  3440. }
  3441. class Element {
  3442. static defaults = {};
  3443. static defaultRoutes = undefined;
  3444. x;
  3445. y;
  3446. active = false;
  3447. options;
  3448. $animations;
  3449. tooltipPosition(useFinalPosition) {
  3450. const { x , y } = this.getProps([
  3451. 'x',
  3452. 'y'
  3453. ], useFinalPosition);
  3454. return {
  3455. x,
  3456. y
  3457. };
  3458. }
  3459. hasValue() {
  3460. return isNumber(this.x) && isNumber(this.y);
  3461. }
  3462. getProps(props, final) {
  3463. const anims = this.$animations;
  3464. if (!final || !anims) {
  3465. // let's not create an object, if not needed
  3466. return this;
  3467. }
  3468. const ret = {};
  3469. props.forEach((prop)=>{
  3470. ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
  3471. });
  3472. return ret;
  3473. }
  3474. }
  3475. function autoSkip(scale, ticks) {
  3476. const tickOpts = scale.options.ticks;
  3477. const determinedMaxTicks = determineMaxTicks(scale);
  3478. const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
  3479. const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
  3480. const numMajorIndices = majorIndices.length;
  3481. const first = majorIndices[0];
  3482. const last = majorIndices[numMajorIndices - 1];
  3483. const newTicks = [];
  3484. if (numMajorIndices > ticksLimit) {
  3485. skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
  3486. return newTicks;
  3487. }
  3488. const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
  3489. if (numMajorIndices > 0) {
  3490. let i, ilen;
  3491. const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
  3492. skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
  3493. for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
  3494. skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
  3495. }
  3496. skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
  3497. return newTicks;
  3498. }
  3499. skip(ticks, newTicks, spacing);
  3500. return newTicks;
  3501. }
  3502. function determineMaxTicks(scale) {
  3503. const offset = scale.options.offset;
  3504. const tickLength = scale._tickSize();
  3505. const maxScale = scale._length / tickLength + (offset ? 0 : 1);
  3506. const maxChart = scale._maxLength / tickLength;
  3507. return Math.floor(Math.min(maxScale, maxChart));
  3508. }
  3509. function calculateSpacing(majorIndices, ticks, ticksLimit) {
  3510. const evenMajorSpacing = getEvenSpacing(majorIndices);
  3511. const spacing = ticks.length / ticksLimit;
  3512. if (!evenMajorSpacing) {
  3513. return Math.max(spacing, 1);
  3514. }
  3515. const factors = _factorize(evenMajorSpacing);
  3516. for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
  3517. const factor = factors[i];
  3518. if (factor > spacing) {
  3519. return factor;
  3520. }
  3521. }
  3522. return Math.max(spacing, 1);
  3523. }
  3524. function getMajorIndices(ticks) {
  3525. const result = [];
  3526. let i, ilen;
  3527. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3528. if (ticks[i].major) {
  3529. result.push(i);
  3530. }
  3531. }
  3532. return result;
  3533. }
  3534. function skipMajors(ticks, newTicks, majorIndices, spacing) {
  3535. let count = 0;
  3536. let next = majorIndices[0];
  3537. let i;
  3538. spacing = Math.ceil(spacing);
  3539. for(i = 0; i < ticks.length; i++){
  3540. if (i === next) {
  3541. newTicks.push(ticks[i]);
  3542. count++;
  3543. next = majorIndices[count * spacing];
  3544. }
  3545. }
  3546. }
  3547. function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
  3548. const start = valueOrDefault(majorStart, 0);
  3549. const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
  3550. let count = 0;
  3551. let length, i, next;
  3552. spacing = Math.ceil(spacing);
  3553. if (majorEnd) {
  3554. length = majorEnd - majorStart;
  3555. spacing = length / Math.floor(length / spacing);
  3556. }
  3557. next = start;
  3558. while(next < 0){
  3559. count++;
  3560. next = Math.round(start + count * spacing);
  3561. }
  3562. for(i = Math.max(start, 0); i < end; i++){
  3563. if (i === next) {
  3564. newTicks.push(ticks[i]);
  3565. count++;
  3566. next = Math.round(start + count * spacing);
  3567. }
  3568. }
  3569. }
  3570. function getEvenSpacing(arr) {
  3571. const len = arr.length;
  3572. let i, diff;
  3573. if (len < 2) {
  3574. return false;
  3575. }
  3576. for(diff = arr[0], i = 1; i < len; ++i){
  3577. if (arr[i] - arr[i - 1] !== diff) {
  3578. return false;
  3579. }
  3580. }
  3581. return diff;
  3582. }
  3583. const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  3584. const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  3585. const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
  3586. function sample(arr, numItems) {
  3587. const result = [];
  3588. const increment = arr.length / numItems;
  3589. const len = arr.length;
  3590. let i = 0;
  3591. for(; i < len; i += increment){
  3592. result.push(arr[Math.floor(i)]);
  3593. }
  3594. return result;
  3595. }
  3596. function getPixelForGridLine(scale, index, offsetGridLines) {
  3597. const length = scale.ticks.length;
  3598. const validIndex = Math.min(index, length - 1);
  3599. const start = scale._startPixel;
  3600. const end = scale._endPixel;
  3601. const epsilon = 1e-6;
  3602. let lineValue = scale.getPixelForTick(validIndex);
  3603. let offset;
  3604. if (offsetGridLines) {
  3605. if (length === 1) {
  3606. offset = Math.max(lineValue - start, end - lineValue);
  3607. } else if (index === 0) {
  3608. offset = (scale.getPixelForTick(1) - lineValue) / 2;
  3609. } else {
  3610. offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
  3611. }
  3612. lineValue += validIndex < index ? offset : -offset;
  3613. if (lineValue < start - epsilon || lineValue > end + epsilon) {
  3614. return;
  3615. }
  3616. }
  3617. return lineValue;
  3618. }
  3619. function garbageCollect(caches, length) {
  3620. each(caches, (cache)=>{
  3621. const gc = cache.gc;
  3622. const gcLen = gc.length / 2;
  3623. let i;
  3624. if (gcLen > length) {
  3625. for(i = 0; i < gcLen; ++i){
  3626. delete cache.data[gc[i]];
  3627. }
  3628. gc.splice(0, gcLen);
  3629. }
  3630. });
  3631. }
  3632. function getTickMarkLength(options) {
  3633. return options.drawTicks ? options.tickLength : 0;
  3634. }
  3635. function getTitleHeight(options, fallback) {
  3636. if (!options.display) {
  3637. return 0;
  3638. }
  3639. const font = toFont(options.font, fallback);
  3640. const padding = toPadding(options.padding);
  3641. const lines = isArray(options.text) ? options.text.length : 1;
  3642. return lines * font.lineHeight + padding.height;
  3643. }
  3644. function createScaleContext(parent, scale) {
  3645. return createContext(parent, {
  3646. scale,
  3647. type: 'scale'
  3648. });
  3649. }
  3650. function createTickContext(parent, index, tick) {
  3651. return createContext(parent, {
  3652. tick,
  3653. index,
  3654. type: 'tick'
  3655. });
  3656. }
  3657. function titleAlign(align, position, reverse) {
  3658. let ret = _toLeftRightCenter(align);
  3659. if (reverse && position !== 'right' || !reverse && position === 'right') {
  3660. ret = reverseAlign(ret);
  3661. }
  3662. return ret;
  3663. }
  3664. function titleArgs(scale, offset, position, align) {
  3665. const { top , left , bottom , right , chart } = scale;
  3666. const { chartArea , scales } = chart;
  3667. let rotation = 0;
  3668. let maxWidth, titleX, titleY;
  3669. const height = bottom - top;
  3670. const width = right - left;
  3671. if (scale.isHorizontal()) {
  3672. titleX = _alignStartEnd(align, left, right);
  3673. if (isObject(position)) {
  3674. const positionAxisID = Object.keys(position)[0];
  3675. const value = position[positionAxisID];
  3676. titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
  3677. } else if (position === 'center') {
  3678. titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
  3679. } else {
  3680. titleY = offsetFromEdge(scale, position, offset);
  3681. }
  3682. maxWidth = right - left;
  3683. } else {
  3684. if (isObject(position)) {
  3685. const positionAxisID = Object.keys(position)[0];
  3686. const value = position[positionAxisID];
  3687. titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
  3688. } else if (position === 'center') {
  3689. titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
  3690. } else {
  3691. titleX = offsetFromEdge(scale, position, offset);
  3692. }
  3693. titleY = _alignStartEnd(align, bottom, top);
  3694. rotation = position === 'left' ? -HALF_PI : HALF_PI;
  3695. }
  3696. return {
  3697. titleX,
  3698. titleY,
  3699. maxWidth,
  3700. rotation
  3701. };
  3702. }
  3703. class Scale extends Element {
  3704. constructor(cfg){
  3705. super();
  3706. this.id = cfg.id;
  3707. this.type = cfg.type;
  3708. this.options = undefined;
  3709. this.ctx = cfg.ctx;
  3710. this.chart = cfg.chart;
  3711. this.top = undefined;
  3712. this.bottom = undefined;
  3713. this.left = undefined;
  3714. this.right = undefined;
  3715. this.width = undefined;
  3716. this.height = undefined;
  3717. this._margins = {
  3718. left: 0,
  3719. right: 0,
  3720. top: 0,
  3721. bottom: 0
  3722. };
  3723. this.maxWidth = undefined;
  3724. this.maxHeight = undefined;
  3725. this.paddingTop = undefined;
  3726. this.paddingBottom = undefined;
  3727. this.paddingLeft = undefined;
  3728. this.paddingRight = undefined;
  3729. this.axis = undefined;
  3730. this.labelRotation = undefined;
  3731. this.min = undefined;
  3732. this.max = undefined;
  3733. this._range = undefined;
  3734. this.ticks = [];
  3735. this._gridLineItems = null;
  3736. this._labelItems = null;
  3737. this._labelSizes = null;
  3738. this._length = 0;
  3739. this._maxLength = 0;
  3740. this._longestTextCache = {};
  3741. this._startPixel = undefined;
  3742. this._endPixel = undefined;
  3743. this._reversePixels = false;
  3744. this._userMax = undefined;
  3745. this._userMin = undefined;
  3746. this._suggestedMax = undefined;
  3747. this._suggestedMin = undefined;
  3748. this._ticksLength = 0;
  3749. this._borderValue = 0;
  3750. this._cache = {};
  3751. this._dataLimitsCached = false;
  3752. this.$context = undefined;
  3753. }
  3754. init(options) {
  3755. this.options = options.setContext(this.getContext());
  3756. this.axis = options.axis;
  3757. this._userMin = this.parse(options.min);
  3758. this._userMax = this.parse(options.max);
  3759. this._suggestedMin = this.parse(options.suggestedMin);
  3760. this._suggestedMax = this.parse(options.suggestedMax);
  3761. }
  3762. parse(raw, index) {
  3763. return raw;
  3764. }
  3765. getUserBounds() {
  3766. let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
  3767. _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
  3768. _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
  3769. _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
  3770. _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
  3771. return {
  3772. min: finiteOrDefault(_userMin, _suggestedMin),
  3773. max: finiteOrDefault(_userMax, _suggestedMax),
  3774. minDefined: isNumberFinite(_userMin),
  3775. maxDefined: isNumberFinite(_userMax)
  3776. };
  3777. }
  3778. getMinMax(canStack) {
  3779. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  3780. let range;
  3781. if (minDefined && maxDefined) {
  3782. return {
  3783. min,
  3784. max
  3785. };
  3786. }
  3787. const metas = this.getMatchingVisibleMetas();
  3788. for(let i = 0, ilen = metas.length; i < ilen; ++i){
  3789. range = metas[i].controller.getMinMax(this, canStack);
  3790. if (!minDefined) {
  3791. min = Math.min(min, range.min);
  3792. }
  3793. if (!maxDefined) {
  3794. max = Math.max(max, range.max);
  3795. }
  3796. }
  3797. min = maxDefined && min > max ? max : min;
  3798. max = minDefined && min > max ? min : max;
  3799. return {
  3800. min: finiteOrDefault(min, finiteOrDefault(max, min)),
  3801. max: finiteOrDefault(max, finiteOrDefault(min, max))
  3802. };
  3803. }
  3804. getPadding() {
  3805. return {
  3806. left: this.paddingLeft || 0,
  3807. top: this.paddingTop || 0,
  3808. right: this.paddingRight || 0,
  3809. bottom: this.paddingBottom || 0
  3810. };
  3811. }
  3812. getTicks() {
  3813. return this.ticks;
  3814. }
  3815. getLabels() {
  3816. const data = this.chart.data;
  3817. return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
  3818. }
  3819. getLabelItems(chartArea = this.chart.chartArea) {
  3820. const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
  3821. return items;
  3822. }
  3823. beforeLayout() {
  3824. this._cache = {};
  3825. this._dataLimitsCached = false;
  3826. }
  3827. beforeUpdate() {
  3828. callback(this.options.beforeUpdate, [
  3829. this
  3830. ]);
  3831. }
  3832. update(maxWidth, maxHeight, margins) {
  3833. const { beginAtZero , grace , ticks: tickOpts } = this.options;
  3834. const sampleSize = tickOpts.sampleSize;
  3835. this.beforeUpdate();
  3836. this.maxWidth = maxWidth;
  3837. this.maxHeight = maxHeight;
  3838. this._margins = margins = Object.assign({
  3839. left: 0,
  3840. right: 0,
  3841. top: 0,
  3842. bottom: 0
  3843. }, margins);
  3844. this.ticks = null;
  3845. this._labelSizes = null;
  3846. this._gridLineItems = null;
  3847. this._labelItems = null;
  3848. this.beforeSetDimensions();
  3849. this.setDimensions();
  3850. this.afterSetDimensions();
  3851. this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
  3852. if (!this._dataLimitsCached) {
  3853. this.beforeDataLimits();
  3854. this.determineDataLimits();
  3855. this.afterDataLimits();
  3856. this._range = _addGrace(this, grace, beginAtZero);
  3857. this._dataLimitsCached = true;
  3858. }
  3859. this.beforeBuildTicks();
  3860. this.ticks = this.buildTicks() || [];
  3861. this.afterBuildTicks();
  3862. const samplingEnabled = sampleSize < this.ticks.length;
  3863. this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
  3864. this.configure();
  3865. this.beforeCalculateLabelRotation();
  3866. this.calculateLabelRotation();
  3867. this.afterCalculateLabelRotation();
  3868. if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
  3869. this.ticks = autoSkip(this, this.ticks);
  3870. this._labelSizes = null;
  3871. this.afterAutoSkip();
  3872. }
  3873. if (samplingEnabled) {
  3874. this._convertTicksToLabels(this.ticks);
  3875. }
  3876. this.beforeFit();
  3877. this.fit();
  3878. this.afterFit();
  3879. this.afterUpdate();
  3880. }
  3881. configure() {
  3882. let reversePixels = this.options.reverse;
  3883. let startPixel, endPixel;
  3884. if (this.isHorizontal()) {
  3885. startPixel = this.left;
  3886. endPixel = this.right;
  3887. } else {
  3888. startPixel = this.top;
  3889. endPixel = this.bottom;
  3890. reversePixels = !reversePixels;
  3891. }
  3892. this._startPixel = startPixel;
  3893. this._endPixel = endPixel;
  3894. this._reversePixels = reversePixels;
  3895. this._length = endPixel - startPixel;
  3896. this._alignToPixels = this.options.alignToPixels;
  3897. }
  3898. afterUpdate() {
  3899. callback(this.options.afterUpdate, [
  3900. this
  3901. ]);
  3902. }
  3903. beforeSetDimensions() {
  3904. callback(this.options.beforeSetDimensions, [
  3905. this
  3906. ]);
  3907. }
  3908. setDimensions() {
  3909. if (this.isHorizontal()) {
  3910. this.width = this.maxWidth;
  3911. this.left = 0;
  3912. this.right = this.width;
  3913. } else {
  3914. this.height = this.maxHeight;
  3915. this.top = 0;
  3916. this.bottom = this.height;
  3917. }
  3918. this.paddingLeft = 0;
  3919. this.paddingTop = 0;
  3920. this.paddingRight = 0;
  3921. this.paddingBottom = 0;
  3922. }
  3923. afterSetDimensions() {
  3924. callback(this.options.afterSetDimensions, [
  3925. this
  3926. ]);
  3927. }
  3928. _callHooks(name) {
  3929. this.chart.notifyPlugins(name, this.getContext());
  3930. callback(this.options[name], [
  3931. this
  3932. ]);
  3933. }
  3934. beforeDataLimits() {
  3935. this._callHooks('beforeDataLimits');
  3936. }
  3937. determineDataLimits() {}
  3938. afterDataLimits() {
  3939. this._callHooks('afterDataLimits');
  3940. }
  3941. beforeBuildTicks() {
  3942. this._callHooks('beforeBuildTicks');
  3943. }
  3944. buildTicks() {
  3945. return [];
  3946. }
  3947. afterBuildTicks() {
  3948. this._callHooks('afterBuildTicks');
  3949. }
  3950. beforeTickToLabelConversion() {
  3951. callback(this.options.beforeTickToLabelConversion, [
  3952. this
  3953. ]);
  3954. }
  3955. generateTickLabels(ticks) {
  3956. const tickOpts = this.options.ticks;
  3957. let i, ilen, tick;
  3958. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3959. tick = ticks[i];
  3960. tick.label = callback(tickOpts.callback, [
  3961. tick.value,
  3962. i,
  3963. ticks
  3964. ], this);
  3965. }
  3966. }
  3967. afterTickToLabelConversion() {
  3968. callback(this.options.afterTickToLabelConversion, [
  3969. this
  3970. ]);
  3971. }
  3972. beforeCalculateLabelRotation() {
  3973. callback(this.options.beforeCalculateLabelRotation, [
  3974. this
  3975. ]);
  3976. }
  3977. calculateLabelRotation() {
  3978. const options = this.options;
  3979. const tickOpts = options.ticks;
  3980. const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
  3981. const minRotation = tickOpts.minRotation || 0;
  3982. const maxRotation = tickOpts.maxRotation;
  3983. let labelRotation = minRotation;
  3984. let tickWidth, maxHeight, maxLabelDiagonal;
  3985. if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
  3986. this.labelRotation = minRotation;
  3987. return;
  3988. }
  3989. const labelSizes = this._getLabelSizes();
  3990. const maxLabelWidth = labelSizes.widest.width;
  3991. const maxLabelHeight = labelSizes.highest.height;
  3992. const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
  3993. tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
  3994. if (maxLabelWidth + 6 > tickWidth) {
  3995. tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
  3996. maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
  3997. maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
  3998. labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
  3999. labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
  4000. }
  4001. this.labelRotation = labelRotation;
  4002. }
  4003. afterCalculateLabelRotation() {
  4004. callback(this.options.afterCalculateLabelRotation, [
  4005. this
  4006. ]);
  4007. }
  4008. afterAutoSkip() {}
  4009. beforeFit() {
  4010. callback(this.options.beforeFit, [
  4011. this
  4012. ]);
  4013. }
  4014. fit() {
  4015. const minSize = {
  4016. width: 0,
  4017. height: 0
  4018. };
  4019. const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
  4020. const display = this._isVisible();
  4021. const isHorizontal = this.isHorizontal();
  4022. if (display) {
  4023. const titleHeight = getTitleHeight(titleOpts, chart.options.font);
  4024. if (isHorizontal) {
  4025. minSize.width = this.maxWidth;
  4026. minSize.height = getTickMarkLength(gridOpts) + titleHeight;
  4027. } else {
  4028. minSize.height = this.maxHeight;
  4029. minSize.width = getTickMarkLength(gridOpts) + titleHeight;
  4030. }
  4031. if (tickOpts.display && this.ticks.length) {
  4032. const { first , last , widest , highest } = this._getLabelSizes();
  4033. const tickPadding = tickOpts.padding * 2;
  4034. const angleRadians = toRadians(this.labelRotation);
  4035. const cos = Math.cos(angleRadians);
  4036. const sin = Math.sin(angleRadians);
  4037. if (isHorizontal) {
  4038. const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
  4039. minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
  4040. } else {
  4041. const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
  4042. minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
  4043. }
  4044. this._calculatePadding(first, last, sin, cos);
  4045. }
  4046. }
  4047. this._handleMargins();
  4048. if (isHorizontal) {
  4049. this.width = this._length = chart.width - this._margins.left - this._margins.right;
  4050. this.height = minSize.height;
  4051. } else {
  4052. this.width = minSize.width;
  4053. this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
  4054. }
  4055. }
  4056. _calculatePadding(first, last, sin, cos) {
  4057. const { ticks: { align , padding } , position } = this.options;
  4058. const isRotated = this.labelRotation !== 0;
  4059. const labelsBelowTicks = position !== 'top' && this.axis === 'x';
  4060. if (this.isHorizontal()) {
  4061. const offsetLeft = this.getPixelForTick(0) - this.left;
  4062. const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
  4063. let paddingLeft = 0;
  4064. let paddingRight = 0;
  4065. if (isRotated) {
  4066. if (labelsBelowTicks) {
  4067. paddingLeft = cos * first.width;
  4068. paddingRight = sin * last.height;
  4069. } else {
  4070. paddingLeft = sin * first.height;
  4071. paddingRight = cos * last.width;
  4072. }
  4073. } else if (align === 'start') {
  4074. paddingRight = last.width;
  4075. } else if (align === 'end') {
  4076. paddingLeft = first.width;
  4077. } else if (align !== 'inner') {
  4078. paddingLeft = first.width / 2;
  4079. paddingRight = last.width / 2;
  4080. }
  4081. this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
  4082. this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
  4083. } else {
  4084. let paddingTop = last.height / 2;
  4085. let paddingBottom = first.height / 2;
  4086. if (align === 'start') {
  4087. paddingTop = 0;
  4088. paddingBottom = first.height;
  4089. } else if (align === 'end') {
  4090. paddingTop = last.height;
  4091. paddingBottom = 0;
  4092. }
  4093. this.paddingTop = paddingTop + padding;
  4094. this.paddingBottom = paddingBottom + padding;
  4095. }
  4096. }
  4097. _handleMargins() {
  4098. if (this._margins) {
  4099. this._margins.left = Math.max(this.paddingLeft, this._margins.left);
  4100. this._margins.top = Math.max(this.paddingTop, this._margins.top);
  4101. this._margins.right = Math.max(this.paddingRight, this._margins.right);
  4102. this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
  4103. }
  4104. }
  4105. afterFit() {
  4106. callback(this.options.afterFit, [
  4107. this
  4108. ]);
  4109. }
  4110. isHorizontal() {
  4111. const { axis , position } = this.options;
  4112. return position === 'top' || position === 'bottom' || axis === 'x';
  4113. }
  4114. isFullSize() {
  4115. return this.options.fullSize;
  4116. }
  4117. _convertTicksToLabels(ticks) {
  4118. this.beforeTickToLabelConversion();
  4119. this.generateTickLabels(ticks);
  4120. let i, ilen;
  4121. for(i = 0, ilen = ticks.length; i < ilen; i++){
  4122. if (isNullOrUndef(ticks[i].label)) {
  4123. ticks.splice(i, 1);
  4124. ilen--;
  4125. i--;
  4126. }
  4127. }
  4128. this.afterTickToLabelConversion();
  4129. }
  4130. _getLabelSizes() {
  4131. let labelSizes = this._labelSizes;
  4132. if (!labelSizes) {
  4133. const sampleSize = this.options.ticks.sampleSize;
  4134. let ticks = this.ticks;
  4135. if (sampleSize < ticks.length) {
  4136. ticks = sample(ticks, sampleSize);
  4137. }
  4138. this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
  4139. }
  4140. return labelSizes;
  4141. }
  4142. _computeLabelSizes(ticks, length, maxTicksLimit) {
  4143. const { ctx , _longestTextCache: caches } = this;
  4144. const widths = [];
  4145. const heights = [];
  4146. const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
  4147. let widestLabelSize = 0;
  4148. let highestLabelSize = 0;
  4149. let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
  4150. for(i = 0; i < length; i += increment){
  4151. label = ticks[i].label;
  4152. tickFont = this._resolveTickFontOptions(i);
  4153. ctx.font = fontString = tickFont.string;
  4154. cache = caches[fontString] = caches[fontString] || {
  4155. data: {},
  4156. gc: []
  4157. };
  4158. lineHeight = tickFont.lineHeight;
  4159. width = height = 0;
  4160. if (!isNullOrUndef(label) && !isArray(label)) {
  4161. width = _measureText(ctx, cache.data, cache.gc, width, label);
  4162. height = lineHeight;
  4163. } else if (isArray(label)) {
  4164. for(j = 0, jlen = label.length; j < jlen; ++j){
  4165. nestedLabel = label[j];
  4166. if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
  4167. width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  4168. height += lineHeight;
  4169. }
  4170. }
  4171. }
  4172. widths.push(width);
  4173. heights.push(height);
  4174. widestLabelSize = Math.max(width, widestLabelSize);
  4175. highestLabelSize = Math.max(height, highestLabelSize);
  4176. }
  4177. garbageCollect(caches, length);
  4178. const widest = widths.indexOf(widestLabelSize);
  4179. const highest = heights.indexOf(highestLabelSize);
  4180. const valueAt = (idx)=>({
  4181. width: widths[idx] || 0,
  4182. height: heights[idx] || 0
  4183. });
  4184. return {
  4185. first: valueAt(0),
  4186. last: valueAt(length - 1),
  4187. widest: valueAt(widest),
  4188. highest: valueAt(highest),
  4189. widths,
  4190. heights
  4191. };
  4192. }
  4193. getLabelForValue(value) {
  4194. return value;
  4195. }
  4196. getPixelForValue(value, index) {
  4197. return NaN;
  4198. }
  4199. getValueForPixel(pixel) {}
  4200. getPixelForTick(index) {
  4201. const ticks = this.ticks;
  4202. if (index < 0 || index > ticks.length - 1) {
  4203. return null;
  4204. }
  4205. return this.getPixelForValue(ticks[index].value);
  4206. }
  4207. getPixelForDecimal(decimal) {
  4208. if (this._reversePixels) {
  4209. decimal = 1 - decimal;
  4210. }
  4211. const pixel = this._startPixel + decimal * this._length;
  4212. return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
  4213. }
  4214. getDecimalForPixel(pixel) {
  4215. const decimal = (pixel - this._startPixel) / this._length;
  4216. return this._reversePixels ? 1 - decimal : decimal;
  4217. }
  4218. getBasePixel() {
  4219. return this.getPixelForValue(this.getBaseValue());
  4220. }
  4221. getBaseValue() {
  4222. const { min , max } = this;
  4223. return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
  4224. }
  4225. getContext(index) {
  4226. const ticks = this.ticks || [];
  4227. if (index >= 0 && index < ticks.length) {
  4228. const tick = ticks[index];
  4229. return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
  4230. }
  4231. return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
  4232. }
  4233. _tickSize() {
  4234. const optionTicks = this.options.ticks;
  4235. const rot = toRadians(this.labelRotation);
  4236. const cos = Math.abs(Math.cos(rot));
  4237. const sin = Math.abs(Math.sin(rot));
  4238. const labelSizes = this._getLabelSizes();
  4239. const padding = optionTicks.autoSkipPadding || 0;
  4240. const w = labelSizes ? labelSizes.widest.width + padding : 0;
  4241. const h = labelSizes ? labelSizes.highest.height + padding : 0;
  4242. return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
  4243. }
  4244. _isVisible() {
  4245. const display = this.options.display;
  4246. if (display !== 'auto') {
  4247. return !!display;
  4248. }
  4249. return this.getMatchingVisibleMetas().length > 0;
  4250. }
  4251. _computeGridLineItems(chartArea) {
  4252. const axis = this.axis;
  4253. const chart = this.chart;
  4254. const options = this.options;
  4255. const { grid , position , border } = options;
  4256. const offset = grid.offset;
  4257. const isHorizontal = this.isHorizontal();
  4258. const ticks = this.ticks;
  4259. const ticksLength = ticks.length + (offset ? 1 : 0);
  4260. const tl = getTickMarkLength(grid);
  4261. const items = [];
  4262. const borderOpts = border.setContext(this.getContext());
  4263. const axisWidth = borderOpts.display ? borderOpts.width : 0;
  4264. const axisHalfWidth = axisWidth / 2;
  4265. const alignBorderValue = function(pixel) {
  4266. return _alignPixel(chart, pixel, axisWidth);
  4267. };
  4268. let borderValue, i, lineValue, alignedLineValue;
  4269. let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
  4270. if (position === 'top') {
  4271. borderValue = alignBorderValue(this.bottom);
  4272. ty1 = this.bottom - tl;
  4273. ty2 = borderValue - axisHalfWidth;
  4274. y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
  4275. y2 = chartArea.bottom;
  4276. } else if (position === 'bottom') {
  4277. borderValue = alignBorderValue(this.top);
  4278. y1 = chartArea.top;
  4279. y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
  4280. ty1 = borderValue + axisHalfWidth;
  4281. ty2 = this.top + tl;
  4282. } else if (position === 'left') {
  4283. borderValue = alignBorderValue(this.right);
  4284. tx1 = this.right - tl;
  4285. tx2 = borderValue - axisHalfWidth;
  4286. x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
  4287. x2 = chartArea.right;
  4288. } else if (position === 'right') {
  4289. borderValue = alignBorderValue(this.left);
  4290. x1 = chartArea.left;
  4291. x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
  4292. tx1 = borderValue + axisHalfWidth;
  4293. tx2 = this.left + tl;
  4294. } else if (axis === 'x') {
  4295. if (position === 'center') {
  4296. borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
  4297. } else if (isObject(position)) {
  4298. const positionAxisID = Object.keys(position)[0];
  4299. const value = position[positionAxisID];
  4300. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  4301. }
  4302. y1 = chartArea.top;
  4303. y2 = chartArea.bottom;
  4304. ty1 = borderValue + axisHalfWidth;
  4305. ty2 = ty1 + tl;
  4306. } else if (axis === 'y') {
  4307. if (position === 'center') {
  4308. borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
  4309. } else if (isObject(position)) {
  4310. const positionAxisID = Object.keys(position)[0];
  4311. const value = position[positionAxisID];
  4312. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  4313. }
  4314. tx1 = borderValue - axisHalfWidth;
  4315. tx2 = tx1 - tl;
  4316. x1 = chartArea.left;
  4317. x2 = chartArea.right;
  4318. }
  4319. const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
  4320. const step = Math.max(1, Math.ceil(ticksLength / limit));
  4321. for(i = 0; i < ticksLength; i += step){
  4322. const context = this.getContext(i);
  4323. const optsAtIndex = grid.setContext(context);
  4324. const optsAtIndexBorder = border.setContext(context);
  4325. const lineWidth = optsAtIndex.lineWidth;
  4326. const lineColor = optsAtIndex.color;
  4327. const borderDash = optsAtIndexBorder.dash || [];
  4328. const borderDashOffset = optsAtIndexBorder.dashOffset;
  4329. const tickWidth = optsAtIndex.tickWidth;
  4330. const tickColor = optsAtIndex.tickColor;
  4331. const tickBorderDash = optsAtIndex.tickBorderDash || [];
  4332. const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
  4333. lineValue = getPixelForGridLine(this, i, offset);
  4334. if (lineValue === undefined) {
  4335. continue;
  4336. }
  4337. alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
  4338. if (isHorizontal) {
  4339. tx1 = tx2 = x1 = x2 = alignedLineValue;
  4340. } else {
  4341. ty1 = ty2 = y1 = y2 = alignedLineValue;
  4342. }
  4343. items.push({
  4344. tx1,
  4345. ty1,
  4346. tx2,
  4347. ty2,
  4348. x1,
  4349. y1,
  4350. x2,
  4351. y2,
  4352. width: lineWidth,
  4353. color: lineColor,
  4354. borderDash,
  4355. borderDashOffset,
  4356. tickWidth,
  4357. tickColor,
  4358. tickBorderDash,
  4359. tickBorderDashOffset
  4360. });
  4361. }
  4362. this._ticksLength = ticksLength;
  4363. this._borderValue = borderValue;
  4364. return items;
  4365. }
  4366. _computeLabelItems(chartArea) {
  4367. const axis = this.axis;
  4368. const options = this.options;
  4369. const { position , ticks: optionTicks } = options;
  4370. const isHorizontal = this.isHorizontal();
  4371. const ticks = this.ticks;
  4372. const { align , crossAlign , padding , mirror } = optionTicks;
  4373. const tl = getTickMarkLength(options.grid);
  4374. const tickAndPadding = tl + padding;
  4375. const hTickAndPadding = mirror ? -padding : tickAndPadding;
  4376. const rotation = -toRadians(this.labelRotation);
  4377. const items = [];
  4378. let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
  4379. let textBaseline = 'middle';
  4380. if (position === 'top') {
  4381. y = this.bottom - hTickAndPadding;
  4382. textAlign = this._getXAxisLabelAlignment();
  4383. } else if (position === 'bottom') {
  4384. y = this.top + hTickAndPadding;
  4385. textAlign = this._getXAxisLabelAlignment();
  4386. } else if (position === 'left') {
  4387. const ret = this._getYAxisLabelAlignment(tl);
  4388. textAlign = ret.textAlign;
  4389. x = ret.x;
  4390. } else if (position === 'right') {
  4391. const ret = this._getYAxisLabelAlignment(tl);
  4392. textAlign = ret.textAlign;
  4393. x = ret.x;
  4394. } else if (axis === 'x') {
  4395. if (position === 'center') {
  4396. y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
  4397. } else if (isObject(position)) {
  4398. const positionAxisID = Object.keys(position)[0];
  4399. const value = position[positionAxisID];
  4400. y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
  4401. }
  4402. textAlign = this._getXAxisLabelAlignment();
  4403. } else if (axis === 'y') {
  4404. if (position === 'center') {
  4405. x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
  4406. } else if (isObject(position)) {
  4407. const positionAxisID = Object.keys(position)[0];
  4408. const value = position[positionAxisID];
  4409. x = this.chart.scales[positionAxisID].getPixelForValue(value);
  4410. }
  4411. textAlign = this._getYAxisLabelAlignment(tl).textAlign;
  4412. }
  4413. if (axis === 'y') {
  4414. if (align === 'start') {
  4415. textBaseline = 'top';
  4416. } else if (align === 'end') {
  4417. textBaseline = 'bottom';
  4418. }
  4419. }
  4420. const labelSizes = this._getLabelSizes();
  4421. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  4422. tick = ticks[i];
  4423. label = tick.label;
  4424. const optsAtIndex = optionTicks.setContext(this.getContext(i));
  4425. pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
  4426. font = this._resolveTickFontOptions(i);
  4427. lineHeight = font.lineHeight;
  4428. lineCount = isArray(label) ? label.length : 1;
  4429. const halfCount = lineCount / 2;
  4430. const color = optsAtIndex.color;
  4431. const strokeColor = optsAtIndex.textStrokeColor;
  4432. const strokeWidth = optsAtIndex.textStrokeWidth;
  4433. let tickTextAlign = textAlign;
  4434. if (isHorizontal) {
  4435. x = pixel;
  4436. if (textAlign === 'inner') {
  4437. if (i === ilen - 1) {
  4438. tickTextAlign = !this.options.reverse ? 'right' : 'left';
  4439. } else if (i === 0) {
  4440. tickTextAlign = !this.options.reverse ? 'left' : 'right';
  4441. } else {
  4442. tickTextAlign = 'center';
  4443. }
  4444. }
  4445. if (position === 'top') {
  4446. if (crossAlign === 'near' || rotation !== 0) {
  4447. textOffset = -lineCount * lineHeight + lineHeight / 2;
  4448. } else if (crossAlign === 'center') {
  4449. textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
  4450. } else {
  4451. textOffset = -labelSizes.highest.height + lineHeight / 2;
  4452. }
  4453. } else {
  4454. if (crossAlign === 'near' || rotation !== 0) {
  4455. textOffset = lineHeight / 2;
  4456. } else if (crossAlign === 'center') {
  4457. textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
  4458. } else {
  4459. textOffset = labelSizes.highest.height - lineCount * lineHeight;
  4460. }
  4461. }
  4462. if (mirror) {
  4463. textOffset *= -1;
  4464. }
  4465. if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
  4466. x += lineHeight / 2 * Math.sin(rotation);
  4467. }
  4468. } else {
  4469. y = pixel;
  4470. textOffset = (1 - lineCount) * lineHeight / 2;
  4471. }
  4472. let backdrop;
  4473. if (optsAtIndex.showLabelBackdrop) {
  4474. const labelPadding = toPadding(optsAtIndex.backdropPadding);
  4475. const height = labelSizes.heights[i];
  4476. const width = labelSizes.widths[i];
  4477. let top = textOffset - labelPadding.top;
  4478. let left = 0 - labelPadding.left;
  4479. switch(textBaseline){
  4480. case 'middle':
  4481. top -= height / 2;
  4482. break;
  4483. case 'bottom':
  4484. top -= height;
  4485. break;
  4486. }
  4487. switch(textAlign){
  4488. case 'center':
  4489. left -= width / 2;
  4490. break;
  4491. case 'right':
  4492. left -= width;
  4493. break;
  4494. }
  4495. backdrop = {
  4496. left,
  4497. top,
  4498. width: width + labelPadding.width,
  4499. height: height + labelPadding.height,
  4500. color: optsAtIndex.backdropColor
  4501. };
  4502. }
  4503. items.push({
  4504. label,
  4505. font,
  4506. textOffset,
  4507. options: {
  4508. rotation,
  4509. color,
  4510. strokeColor,
  4511. strokeWidth,
  4512. textAlign: tickTextAlign,
  4513. textBaseline,
  4514. translation: [
  4515. x,
  4516. y
  4517. ],
  4518. backdrop
  4519. }
  4520. });
  4521. }
  4522. return items;
  4523. }
  4524. _getXAxisLabelAlignment() {
  4525. const { position , ticks } = this.options;
  4526. const rotation = -toRadians(this.labelRotation);
  4527. if (rotation) {
  4528. return position === 'top' ? 'left' : 'right';
  4529. }
  4530. let align = 'center';
  4531. if (ticks.align === 'start') {
  4532. align = 'left';
  4533. } else if (ticks.align === 'end') {
  4534. align = 'right';
  4535. } else if (ticks.align === 'inner') {
  4536. align = 'inner';
  4537. }
  4538. return align;
  4539. }
  4540. _getYAxisLabelAlignment(tl) {
  4541. const { position , ticks: { crossAlign , mirror , padding } } = this.options;
  4542. const labelSizes = this._getLabelSizes();
  4543. const tickAndPadding = tl + padding;
  4544. const widest = labelSizes.widest.width;
  4545. let textAlign;
  4546. let x;
  4547. if (position === 'left') {
  4548. if (mirror) {
  4549. x = this.right + padding;
  4550. if (crossAlign === 'near') {
  4551. textAlign = 'left';
  4552. } else if (crossAlign === 'center') {
  4553. textAlign = 'center';
  4554. x += widest / 2;
  4555. } else {
  4556. textAlign = 'right';
  4557. x += widest;
  4558. }
  4559. } else {
  4560. x = this.right - tickAndPadding;
  4561. if (crossAlign === 'near') {
  4562. textAlign = 'right';
  4563. } else if (crossAlign === 'center') {
  4564. textAlign = 'center';
  4565. x -= widest / 2;
  4566. } else {
  4567. textAlign = 'left';
  4568. x = this.left;
  4569. }
  4570. }
  4571. } else if (position === 'right') {
  4572. if (mirror) {
  4573. x = this.left + padding;
  4574. if (crossAlign === 'near') {
  4575. textAlign = 'right';
  4576. } else if (crossAlign === 'center') {
  4577. textAlign = 'center';
  4578. x -= widest / 2;
  4579. } else {
  4580. textAlign = 'left';
  4581. x -= widest;
  4582. }
  4583. } else {
  4584. x = this.left + tickAndPadding;
  4585. if (crossAlign === 'near') {
  4586. textAlign = 'left';
  4587. } else if (crossAlign === 'center') {
  4588. textAlign = 'center';
  4589. x += widest / 2;
  4590. } else {
  4591. textAlign = 'right';
  4592. x = this.right;
  4593. }
  4594. }
  4595. } else {
  4596. textAlign = 'right';
  4597. }
  4598. return {
  4599. textAlign,
  4600. x
  4601. };
  4602. }
  4603. _computeLabelArea() {
  4604. if (this.options.ticks.mirror) {
  4605. return;
  4606. }
  4607. const chart = this.chart;
  4608. const position = this.options.position;
  4609. if (position === 'left' || position === 'right') {
  4610. return {
  4611. top: 0,
  4612. left: this.left,
  4613. bottom: chart.height,
  4614. right: this.right
  4615. };
  4616. }
  4617. if (position === 'top' || position === 'bottom') {
  4618. return {
  4619. top: this.top,
  4620. left: 0,
  4621. bottom: this.bottom,
  4622. right: chart.width
  4623. };
  4624. }
  4625. }
  4626. drawBackground() {
  4627. const { ctx , options: { backgroundColor } , left , top , width , height } = this;
  4628. if (backgroundColor) {
  4629. ctx.save();
  4630. ctx.fillStyle = backgroundColor;
  4631. ctx.fillRect(left, top, width, height);
  4632. ctx.restore();
  4633. }
  4634. }
  4635. getLineWidthForValue(value) {
  4636. const grid = this.options.grid;
  4637. if (!this._isVisible() || !grid.display) {
  4638. return 0;
  4639. }
  4640. const ticks = this.ticks;
  4641. const index = ticks.findIndex((t)=>t.value === value);
  4642. if (index >= 0) {
  4643. const opts = grid.setContext(this.getContext(index));
  4644. return opts.lineWidth;
  4645. }
  4646. return 0;
  4647. }
  4648. drawGrid(chartArea) {
  4649. const grid = this.options.grid;
  4650. const ctx = this.ctx;
  4651. const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
  4652. let i, ilen;
  4653. const drawLine = (p1, p2, style)=>{
  4654. if (!style.width || !style.color) {
  4655. return;
  4656. }
  4657. ctx.save();
  4658. ctx.lineWidth = style.width;
  4659. ctx.strokeStyle = style.color;
  4660. ctx.setLineDash(style.borderDash || []);
  4661. ctx.lineDashOffset = style.borderDashOffset;
  4662. ctx.beginPath();
  4663. ctx.moveTo(p1.x, p1.y);
  4664. ctx.lineTo(p2.x, p2.y);
  4665. ctx.stroke();
  4666. ctx.restore();
  4667. };
  4668. if (grid.display) {
  4669. for(i = 0, ilen = items.length; i < ilen; ++i){
  4670. const item = items[i];
  4671. if (grid.drawOnChartArea) {
  4672. drawLine({
  4673. x: item.x1,
  4674. y: item.y1
  4675. }, {
  4676. x: item.x2,
  4677. y: item.y2
  4678. }, item);
  4679. }
  4680. if (grid.drawTicks) {
  4681. drawLine({
  4682. x: item.tx1,
  4683. y: item.ty1
  4684. }, {
  4685. x: item.tx2,
  4686. y: item.ty2
  4687. }, {
  4688. color: item.tickColor,
  4689. width: item.tickWidth,
  4690. borderDash: item.tickBorderDash,
  4691. borderDashOffset: item.tickBorderDashOffset
  4692. });
  4693. }
  4694. }
  4695. }
  4696. }
  4697. drawBorder() {
  4698. const { chart , ctx , options: { border , grid } } = this;
  4699. const borderOpts = border.setContext(this.getContext());
  4700. const axisWidth = border.display ? borderOpts.width : 0;
  4701. if (!axisWidth) {
  4702. return;
  4703. }
  4704. const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
  4705. const borderValue = this._borderValue;
  4706. let x1, x2, y1, y2;
  4707. if (this.isHorizontal()) {
  4708. x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
  4709. x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
  4710. y1 = y2 = borderValue;
  4711. } else {
  4712. y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
  4713. y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
  4714. x1 = x2 = borderValue;
  4715. }
  4716. ctx.save();
  4717. ctx.lineWidth = borderOpts.width;
  4718. ctx.strokeStyle = borderOpts.color;
  4719. ctx.beginPath();
  4720. ctx.moveTo(x1, y1);
  4721. ctx.lineTo(x2, y2);
  4722. ctx.stroke();
  4723. ctx.restore();
  4724. }
  4725. drawLabels(chartArea) {
  4726. const optionTicks = this.options.ticks;
  4727. if (!optionTicks.display) {
  4728. return;
  4729. }
  4730. const ctx = this.ctx;
  4731. const area = this._computeLabelArea();
  4732. if (area) {
  4733. clipArea(ctx, area);
  4734. }
  4735. const items = this.getLabelItems(chartArea);
  4736. for (const item of items){
  4737. const renderTextOptions = item.options;
  4738. const tickFont = item.font;
  4739. const label = item.label;
  4740. const y = item.textOffset;
  4741. renderText(ctx, label, 0, y, tickFont, renderTextOptions);
  4742. }
  4743. if (area) {
  4744. unclipArea(ctx);
  4745. }
  4746. }
  4747. drawTitle() {
  4748. const { ctx , options: { position , title , reverse } } = this;
  4749. if (!title.display) {
  4750. return;
  4751. }
  4752. const font = toFont(title.font);
  4753. const padding = toPadding(title.padding);
  4754. const align = title.align;
  4755. let offset = font.lineHeight / 2;
  4756. if (position === 'bottom' || position === 'center' || isObject(position)) {
  4757. offset += padding.bottom;
  4758. if (isArray(title.text)) {
  4759. offset += font.lineHeight * (title.text.length - 1);
  4760. }
  4761. } else {
  4762. offset += padding.top;
  4763. }
  4764. const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
  4765. renderText(ctx, title.text, 0, 0, font, {
  4766. color: title.color,
  4767. maxWidth,
  4768. rotation,
  4769. textAlign: titleAlign(align, position, reverse),
  4770. textBaseline: 'middle',
  4771. translation: [
  4772. titleX,
  4773. titleY
  4774. ]
  4775. });
  4776. }
  4777. draw(chartArea) {
  4778. if (!this._isVisible()) {
  4779. return;
  4780. }
  4781. this.drawBackground();
  4782. this.drawGrid(chartArea);
  4783. this.drawBorder();
  4784. this.drawTitle();
  4785. this.drawLabels(chartArea);
  4786. }
  4787. _layers() {
  4788. const opts = this.options;
  4789. const tz = opts.ticks && opts.ticks.z || 0;
  4790. const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
  4791. const bz = valueOrDefault(opts.border && opts.border.z, 0);
  4792. if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
  4793. return [
  4794. {
  4795. z: tz,
  4796. draw: (chartArea)=>{
  4797. this.draw(chartArea);
  4798. }
  4799. }
  4800. ];
  4801. }
  4802. return [
  4803. {
  4804. z: gz,
  4805. draw: (chartArea)=>{
  4806. this.drawBackground();
  4807. this.drawGrid(chartArea);
  4808. this.drawTitle();
  4809. }
  4810. },
  4811. {
  4812. z: bz,
  4813. draw: ()=>{
  4814. this.drawBorder();
  4815. }
  4816. },
  4817. {
  4818. z: tz,
  4819. draw: (chartArea)=>{
  4820. this.drawLabels(chartArea);
  4821. }
  4822. }
  4823. ];
  4824. }
  4825. getMatchingVisibleMetas(type) {
  4826. const metas = this.chart.getSortedVisibleDatasetMetas();
  4827. const axisID = this.axis + 'AxisID';
  4828. const result = [];
  4829. let i, ilen;
  4830. for(i = 0, ilen = metas.length; i < ilen; ++i){
  4831. const meta = metas[i];
  4832. if (meta[axisID] === this.id && (!type || meta.type === type)) {
  4833. result.push(meta);
  4834. }
  4835. }
  4836. return result;
  4837. }
  4838. _resolveTickFontOptions(index) {
  4839. const opts = this.options.ticks.setContext(this.getContext(index));
  4840. return toFont(opts.font);
  4841. }
  4842. _maxDigits() {
  4843. const fontSize = this._resolveTickFontOptions(0).lineHeight;
  4844. return (this.isHorizontal() ? this.width : this.height) / fontSize;
  4845. }
  4846. }
  4847. class TypedRegistry {
  4848. constructor(type, scope, override){
  4849. this.type = type;
  4850. this.scope = scope;
  4851. this.override = override;
  4852. this.items = Object.create(null);
  4853. }
  4854. isForType(type) {
  4855. return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
  4856. }
  4857. register(item) {
  4858. const proto = Object.getPrototypeOf(item);
  4859. let parentScope;
  4860. if (isIChartComponent(proto)) {
  4861. parentScope = this.register(proto);
  4862. }
  4863. const items = this.items;
  4864. const id = item.id;
  4865. const scope = this.scope + '.' + id;
  4866. if (!id) {
  4867. throw new Error('class does not have id: ' + item);
  4868. }
  4869. if (id in items) {
  4870. return scope;
  4871. }
  4872. items[id] = item;
  4873. registerDefaults(item, scope, parentScope);
  4874. if (this.override) {
  4875. defaults.override(item.id, item.overrides);
  4876. }
  4877. return scope;
  4878. }
  4879. get(id) {
  4880. return this.items[id];
  4881. }
  4882. unregister(item) {
  4883. const items = this.items;
  4884. const id = item.id;
  4885. const scope = this.scope;
  4886. if (id in items) {
  4887. delete items[id];
  4888. }
  4889. if (scope && id in defaults[scope]) {
  4890. delete defaults[scope][id];
  4891. if (this.override) {
  4892. delete overrides[id];
  4893. }
  4894. }
  4895. }
  4896. }
  4897. function registerDefaults(item, scope, parentScope) {
  4898. const itemDefaults = merge(Object.create(null), [
  4899. parentScope ? defaults.get(parentScope) : {},
  4900. defaults.get(scope),
  4901. item.defaults
  4902. ]);
  4903. defaults.set(scope, itemDefaults);
  4904. if (item.defaultRoutes) {
  4905. routeDefaults(scope, item.defaultRoutes);
  4906. }
  4907. if (item.descriptors) {
  4908. defaults.describe(scope, item.descriptors);
  4909. }
  4910. }
  4911. function routeDefaults(scope, routes) {
  4912. Object.keys(routes).forEach((property)=>{
  4913. const propertyParts = property.split('.');
  4914. const sourceName = propertyParts.pop();
  4915. const sourceScope = [
  4916. scope
  4917. ].concat(propertyParts).join('.');
  4918. const parts = routes[property].split('.');
  4919. const targetName = parts.pop();
  4920. const targetScope = parts.join('.');
  4921. defaults.route(sourceScope, sourceName, targetScope, targetName);
  4922. });
  4923. }
  4924. function isIChartComponent(proto) {
  4925. return 'id' in proto && 'defaults' in proto;
  4926. }
  4927. class Registry {
  4928. constructor(){
  4929. this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
  4930. this.elements = new TypedRegistry(Element, 'elements');
  4931. this.plugins = new TypedRegistry(Object, 'plugins');
  4932. this.scales = new TypedRegistry(Scale, 'scales');
  4933. this._typedRegistries = [
  4934. this.controllers,
  4935. this.scales,
  4936. this.elements
  4937. ];
  4938. }
  4939. add(...args) {
  4940. this._each('register', args);
  4941. }
  4942. remove(...args) {
  4943. this._each('unregister', args);
  4944. }
  4945. addControllers(...args) {
  4946. this._each('register', args, this.controllers);
  4947. }
  4948. addElements(...args) {
  4949. this._each('register', args, this.elements);
  4950. }
  4951. addPlugins(...args) {
  4952. this._each('register', args, this.plugins);
  4953. }
  4954. addScales(...args) {
  4955. this._each('register', args, this.scales);
  4956. }
  4957. getController(id) {
  4958. return this._get(id, this.controllers, 'controller');
  4959. }
  4960. getElement(id) {
  4961. return this._get(id, this.elements, 'element');
  4962. }
  4963. getPlugin(id) {
  4964. return this._get(id, this.plugins, 'plugin');
  4965. }
  4966. getScale(id) {
  4967. return this._get(id, this.scales, 'scale');
  4968. }
  4969. removeControllers(...args) {
  4970. this._each('unregister', args, this.controllers);
  4971. }
  4972. removeElements(...args) {
  4973. this._each('unregister', args, this.elements);
  4974. }
  4975. removePlugins(...args) {
  4976. this._each('unregister', args, this.plugins);
  4977. }
  4978. removeScales(...args) {
  4979. this._each('unregister', args, this.scales);
  4980. }
  4981. _each(method, args, typedRegistry) {
  4982. [
  4983. ...args
  4984. ].forEach((arg)=>{
  4985. const reg = typedRegistry || this._getRegistryForType(arg);
  4986. if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
  4987. this._exec(method, reg, arg);
  4988. } else {
  4989. each(arg, (item)=>{
  4990. const itemReg = typedRegistry || this._getRegistryForType(item);
  4991. this._exec(method, itemReg, item);
  4992. });
  4993. }
  4994. });
  4995. }
  4996. _exec(method, registry, component) {
  4997. const camelMethod = _capitalize(method);
  4998. callback(component['before' + camelMethod], [], component);
  4999. registry[method](component);
  5000. callback(component['after' + camelMethod], [], component);
  5001. }
  5002. _getRegistryForType(type) {
  5003. for(let i = 0; i < this._typedRegistries.length; i++){
  5004. const reg = this._typedRegistries[i];
  5005. if (reg.isForType(type)) {
  5006. return reg;
  5007. }
  5008. }
  5009. return this.plugins;
  5010. }
  5011. _get(id, typedRegistry, type) {
  5012. const item = typedRegistry.get(id);
  5013. if (item === undefined) {
  5014. throw new Error('"' + id + '" is not a registered ' + type + '.');
  5015. }
  5016. return item;
  5017. }
  5018. }
  5019. var registry = /* #__PURE__ */ new Registry();
  5020. class PluginService {
  5021. constructor(){
  5022. this._init = [];
  5023. }
  5024. notify(chart, hook, args, filter) {
  5025. if (hook === 'beforeInit') {
  5026. this._init = this._createDescriptors(chart, true);
  5027. this._notify(this._init, chart, 'install');
  5028. }
  5029. const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
  5030. const result = this._notify(descriptors, chart, hook, args);
  5031. if (hook === 'afterDestroy') {
  5032. this._notify(descriptors, chart, 'stop');
  5033. this._notify(this._init, chart, 'uninstall');
  5034. }
  5035. return result;
  5036. }
  5037. _notify(descriptors, chart, hook, args) {
  5038. args = args || {};
  5039. for (const descriptor of descriptors){
  5040. const plugin = descriptor.plugin;
  5041. const method = plugin[hook];
  5042. const params = [
  5043. chart,
  5044. args,
  5045. descriptor.options
  5046. ];
  5047. if (callback(method, params, plugin) === false && args.cancelable) {
  5048. return false;
  5049. }
  5050. }
  5051. return true;
  5052. }
  5053. invalidate() {
  5054. if (!isNullOrUndef(this._cache)) {
  5055. this._oldCache = this._cache;
  5056. this._cache = undefined;
  5057. }
  5058. }
  5059. _descriptors(chart) {
  5060. if (this._cache) {
  5061. return this._cache;
  5062. }
  5063. const descriptors = this._cache = this._createDescriptors(chart);
  5064. this._notifyStateChanges(chart);
  5065. return descriptors;
  5066. }
  5067. _createDescriptors(chart, all) {
  5068. const config = chart && chart.config;
  5069. const options = valueOrDefault(config.options && config.options.plugins, {});
  5070. const plugins = allPlugins(config);
  5071. return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
  5072. }
  5073. _notifyStateChanges(chart) {
  5074. const previousDescriptors = this._oldCache || [];
  5075. const descriptors = this._cache;
  5076. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
  5077. this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
  5078. this._notify(diff(descriptors, previousDescriptors), chart, 'start');
  5079. }
  5080. }
  5081. function allPlugins(config) {
  5082. const localIds = {};
  5083. const plugins = [];
  5084. const keys = Object.keys(registry.plugins.items);
  5085. for(let i = 0; i < keys.length; i++){
  5086. plugins.push(registry.getPlugin(keys[i]));
  5087. }
  5088. const local = config.plugins || [];
  5089. for(let i = 0; i < local.length; i++){
  5090. const plugin = local[i];
  5091. if (plugins.indexOf(plugin) === -1) {
  5092. plugins.push(plugin);
  5093. localIds[plugin.id] = true;
  5094. }
  5095. }
  5096. return {
  5097. plugins,
  5098. localIds
  5099. };
  5100. }
  5101. function getOpts(options, all) {
  5102. if (!all && options === false) {
  5103. return null;
  5104. }
  5105. if (options === true) {
  5106. return {};
  5107. }
  5108. return options;
  5109. }
  5110. function createDescriptors(chart, { plugins , localIds }, options, all) {
  5111. const result = [];
  5112. const context = chart.getContext();
  5113. for (const plugin of plugins){
  5114. const id = plugin.id;
  5115. const opts = getOpts(options[id], all);
  5116. if (opts === null) {
  5117. continue;
  5118. }
  5119. result.push({
  5120. plugin,
  5121. options: pluginOpts(chart.config, {
  5122. plugin,
  5123. local: localIds[id]
  5124. }, opts, context)
  5125. });
  5126. }
  5127. return result;
  5128. }
  5129. function pluginOpts(config, { plugin , local }, opts, context) {
  5130. const keys = config.pluginScopeKeys(plugin);
  5131. const scopes = config.getOptionScopes(opts, keys);
  5132. if (local && plugin.defaults) {
  5133. scopes.push(plugin.defaults);
  5134. }
  5135. return config.createResolver(scopes, context, [
  5136. ''
  5137. ], {
  5138. scriptable: false,
  5139. indexable: false,
  5140. allKeys: true
  5141. });
  5142. }
  5143. function getIndexAxis(type, options) {
  5144. const datasetDefaults = defaults.datasets[type] || {};
  5145. const datasetOptions = (options.datasets || {})[type] || {};
  5146. return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  5147. }
  5148. function getAxisFromDefaultScaleID(id, indexAxis) {
  5149. let axis = id;
  5150. if (id === '_index_') {
  5151. axis = indexAxis;
  5152. } else if (id === '_value_') {
  5153. axis = indexAxis === 'x' ? 'y' : 'x';
  5154. }
  5155. return axis;
  5156. }
  5157. function getDefaultScaleIDFromAxis(axis, indexAxis) {
  5158. return axis === indexAxis ? '_index_' : '_value_';
  5159. }
  5160. function idMatchesAxis(id) {
  5161. if (id === 'x' || id === 'y' || id === 'r') {
  5162. return id;
  5163. }
  5164. }
  5165. function axisFromPosition(position) {
  5166. if (position === 'top' || position === 'bottom') {
  5167. return 'x';
  5168. }
  5169. if (position === 'left' || position === 'right') {
  5170. return 'y';
  5171. }
  5172. }
  5173. function determineAxis(id, ...scaleOptions) {
  5174. if (idMatchesAxis(id)) {
  5175. return id;
  5176. }
  5177. for (const opts of scaleOptions){
  5178. const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
  5179. if (axis) {
  5180. return axis;
  5181. }
  5182. }
  5183. throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
  5184. }
  5185. function getAxisFromDataset(id, axis, dataset) {
  5186. if (dataset[axis + 'AxisID'] === id) {
  5187. return {
  5188. axis
  5189. };
  5190. }
  5191. }
  5192. function retrieveAxisFromDatasets(id, config) {
  5193. if (config.data && config.data.datasets) {
  5194. const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id);
  5195. if (boundDs.length) {
  5196. return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
  5197. }
  5198. }
  5199. return {};
  5200. }
  5201. function mergeScaleConfig(config, options) {
  5202. const chartDefaults = overrides[config.type] || {
  5203. scales: {}
  5204. };
  5205. const configScales = options.scales || {};
  5206. const chartIndexAxis = getIndexAxis(config.type, options);
  5207. const scales = Object.create(null);
  5208. Object.keys(configScales).forEach((id)=>{
  5209. const scaleConf = configScales[id];
  5210. if (!isObject(scaleConf)) {
  5211. return console.error(`Invalid scale configuration for scale: ${id}`);
  5212. }
  5213. if (scaleConf._proxy) {
  5214. return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
  5215. }
  5216. const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);
  5217. const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
  5218. const defaultScaleOptions = chartDefaults.scales || {};
  5219. scales[id] = mergeIf(Object.create(null), [
  5220. {
  5221. axis
  5222. },
  5223. scaleConf,
  5224. defaultScaleOptions[axis],
  5225. defaultScaleOptions[defaultId]
  5226. ]);
  5227. });
  5228. config.data.datasets.forEach((dataset)=>{
  5229. const type = dataset.type || config.type;
  5230. const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
  5231. const datasetDefaults = overrides[type] || {};
  5232. const defaultScaleOptions = datasetDefaults.scales || {};
  5233. Object.keys(defaultScaleOptions).forEach((defaultID)=>{
  5234. const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
  5235. const id = dataset[axis + 'AxisID'] || axis;
  5236. scales[id] = scales[id] || Object.create(null);
  5237. mergeIf(scales[id], [
  5238. {
  5239. axis
  5240. },
  5241. configScales[id],
  5242. defaultScaleOptions[defaultID]
  5243. ]);
  5244. });
  5245. });
  5246. Object.keys(scales).forEach((key)=>{
  5247. const scale = scales[key];
  5248. mergeIf(scale, [
  5249. defaults.scales[scale.type],
  5250. defaults.scale
  5251. ]);
  5252. });
  5253. return scales;
  5254. }
  5255. function initOptions(config) {
  5256. const options = config.options || (config.options = {});
  5257. options.plugins = valueOrDefault(options.plugins, {});
  5258. options.scales = mergeScaleConfig(config, options);
  5259. }
  5260. function initData(data) {
  5261. data = data || {};
  5262. data.datasets = data.datasets || [];
  5263. data.labels = data.labels || [];
  5264. return data;
  5265. }
  5266. function initConfig(config) {
  5267. config = config || {};
  5268. config.data = initData(config.data);
  5269. initOptions(config);
  5270. return config;
  5271. }
  5272. const keyCache = new Map();
  5273. const keysCached = new Set();
  5274. function cachedKeys(cacheKey, generate) {
  5275. let keys = keyCache.get(cacheKey);
  5276. if (!keys) {
  5277. keys = generate();
  5278. keyCache.set(cacheKey, keys);
  5279. keysCached.add(keys);
  5280. }
  5281. return keys;
  5282. }
  5283. const addIfFound = (set, obj, key)=>{
  5284. const opts = resolveObjectKey(obj, key);
  5285. if (opts !== undefined) {
  5286. set.add(opts);
  5287. }
  5288. };
  5289. class Config {
  5290. constructor(config){
  5291. this._config = initConfig(config);
  5292. this._scopeCache = new Map();
  5293. this._resolverCache = new Map();
  5294. }
  5295. get platform() {
  5296. return this._config.platform;
  5297. }
  5298. get type() {
  5299. return this._config.type;
  5300. }
  5301. set type(type) {
  5302. this._config.type = type;
  5303. }
  5304. get data() {
  5305. return this._config.data;
  5306. }
  5307. set data(data) {
  5308. this._config.data = initData(data);
  5309. }
  5310. get options() {
  5311. return this._config.options;
  5312. }
  5313. set options(options) {
  5314. this._config.options = options;
  5315. }
  5316. get plugins() {
  5317. return this._config.plugins;
  5318. }
  5319. update() {
  5320. const config = this._config;
  5321. this.clearCache();
  5322. initOptions(config);
  5323. }
  5324. clearCache() {
  5325. this._scopeCache.clear();
  5326. this._resolverCache.clear();
  5327. }
  5328. datasetScopeKeys(datasetType) {
  5329. return cachedKeys(datasetType, ()=>[
  5330. [
  5331. `datasets.${datasetType}`,
  5332. ''
  5333. ]
  5334. ]);
  5335. }
  5336. datasetAnimationScopeKeys(datasetType, transition) {
  5337. return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
  5338. [
  5339. `datasets.${datasetType}.transitions.${transition}`,
  5340. `transitions.${transition}`
  5341. ],
  5342. [
  5343. `datasets.${datasetType}`,
  5344. ''
  5345. ]
  5346. ]);
  5347. }
  5348. datasetElementScopeKeys(datasetType, elementType) {
  5349. return cachedKeys(`${datasetType}-${elementType}`, ()=>[
  5350. [
  5351. `datasets.${datasetType}.elements.${elementType}`,
  5352. `datasets.${datasetType}`,
  5353. `elements.${elementType}`,
  5354. ''
  5355. ]
  5356. ]);
  5357. }
  5358. pluginScopeKeys(plugin) {
  5359. const id = plugin.id;
  5360. const type = this.type;
  5361. return cachedKeys(`${type}-plugin-${id}`, ()=>[
  5362. [
  5363. `plugins.${id}`,
  5364. ...plugin.additionalOptionScopes || []
  5365. ]
  5366. ]);
  5367. }
  5368. _cachedScopes(mainScope, resetCache) {
  5369. const _scopeCache = this._scopeCache;
  5370. let cache = _scopeCache.get(mainScope);
  5371. if (!cache || resetCache) {
  5372. cache = new Map();
  5373. _scopeCache.set(mainScope, cache);
  5374. }
  5375. return cache;
  5376. }
  5377. getOptionScopes(mainScope, keyLists, resetCache) {
  5378. const { options , type } = this;
  5379. const cache = this._cachedScopes(mainScope, resetCache);
  5380. const cached = cache.get(keyLists);
  5381. if (cached) {
  5382. return cached;
  5383. }
  5384. const scopes = new Set();
  5385. keyLists.forEach((keys)=>{
  5386. if (mainScope) {
  5387. scopes.add(mainScope);
  5388. keys.forEach((key)=>addIfFound(scopes, mainScope, key));
  5389. }
  5390. keys.forEach((key)=>addIfFound(scopes, options, key));
  5391. keys.forEach((key)=>addIfFound(scopes, overrides[type] || {}, key));
  5392. keys.forEach((key)=>addIfFound(scopes, defaults, key));
  5393. keys.forEach((key)=>addIfFound(scopes, descriptors, key));
  5394. });
  5395. const array = Array.from(scopes);
  5396. if (array.length === 0) {
  5397. array.push(Object.create(null));
  5398. }
  5399. if (keysCached.has(keyLists)) {
  5400. cache.set(keyLists, array);
  5401. }
  5402. return array;
  5403. }
  5404. chartOptionScopes() {
  5405. const { options , type } = this;
  5406. return [
  5407. options,
  5408. overrides[type] || {},
  5409. defaults.datasets[type] || {},
  5410. {
  5411. type
  5412. },
  5413. defaults,
  5414. descriptors
  5415. ];
  5416. }
  5417. resolveNamedOptions(scopes, names, context, prefixes = [
  5418. ''
  5419. ]) {
  5420. const result = {
  5421. $shared: true
  5422. };
  5423. const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
  5424. let options = resolver;
  5425. if (needContext(resolver, names)) {
  5426. result.$shared = false;
  5427. context = isFunction(context) ? context() : context;
  5428. const subResolver = this.createResolver(scopes, context, subPrefixes);
  5429. options = _attachContext(resolver, context, subResolver);
  5430. }
  5431. for (const prop of names){
  5432. result[prop] = options[prop];
  5433. }
  5434. return result;
  5435. }
  5436. createResolver(scopes, context, prefixes = [
  5437. ''
  5438. ], descriptorDefaults) {
  5439. const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
  5440. return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
  5441. }
  5442. }
  5443. function getResolver(resolverCache, scopes, prefixes) {
  5444. let cache = resolverCache.get(scopes);
  5445. if (!cache) {
  5446. cache = new Map();
  5447. resolverCache.set(scopes, cache);
  5448. }
  5449. const cacheKey = prefixes.join();
  5450. let cached = cache.get(cacheKey);
  5451. if (!cached) {
  5452. const resolver = _createResolver(scopes, prefixes);
  5453. cached = {
  5454. resolver,
  5455. subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
  5456. };
  5457. cache.set(cacheKey, cached);
  5458. }
  5459. return cached;
  5460. }
  5461. const hasFunction = (value)=>isObject(value) && Object.getOwnPropertyNames(value).reduce((acc, key)=>acc || isFunction(value[key]), false);
  5462. function needContext(proxy, names) {
  5463. const { isScriptable , isIndexable } = _descriptors(proxy);
  5464. for (const prop of names){
  5465. const scriptable = isScriptable(prop);
  5466. const indexable = isIndexable(prop);
  5467. const value = (indexable || scriptable) && proxy[prop];
  5468. if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) {
  5469. return true;
  5470. }
  5471. }
  5472. return false;
  5473. }
  5474. var version = "4.4.0";
  5475. const KNOWN_POSITIONS = [
  5476. 'top',
  5477. 'bottom',
  5478. 'left',
  5479. 'right',
  5480. 'chartArea'
  5481. ];
  5482. function positionIsHorizontal(position, axis) {
  5483. return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
  5484. }
  5485. function compare2Level(l1, l2) {
  5486. return function(a, b) {
  5487. return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
  5488. };
  5489. }
  5490. function onAnimationsComplete(context) {
  5491. const chart = context.chart;
  5492. const animationOptions = chart.options.animation;
  5493. chart.notifyPlugins('afterRender');
  5494. callback(animationOptions && animationOptions.onComplete, [
  5495. context
  5496. ], chart);
  5497. }
  5498. function onAnimationProgress(context) {
  5499. const chart = context.chart;
  5500. const animationOptions = chart.options.animation;
  5501. callback(animationOptions && animationOptions.onProgress, [
  5502. context
  5503. ], chart);
  5504. }
  5505. function getCanvas(item) {
  5506. if (_isDomSupported() && typeof item === 'string') {
  5507. item = document.getElementById(item);
  5508. } else if (item && item.length) {
  5509. item = item[0];
  5510. }
  5511. if (item && item.canvas) {
  5512. item = item.canvas;
  5513. }
  5514. return item;
  5515. }
  5516. const instances = {};
  5517. const getChart = (key)=>{
  5518. const canvas = getCanvas(key);
  5519. return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
  5520. };
  5521. function moveNumericKeys(obj, start, move) {
  5522. const keys = Object.keys(obj);
  5523. for (const key of keys){
  5524. const intKey = +key;
  5525. if (intKey >= start) {
  5526. const value = obj[key];
  5527. delete obj[key];
  5528. if (move > 0 || intKey > start) {
  5529. obj[intKey + move] = value;
  5530. }
  5531. }
  5532. }
  5533. }
  5534. function determineLastEvent(e, lastEvent, inChartArea, isClick) {
  5535. if (!inChartArea || e.type === 'mouseout') {
  5536. return null;
  5537. }
  5538. if (isClick) {
  5539. return lastEvent;
  5540. }
  5541. return e;
  5542. }
  5543. function getSizeForArea(scale, chartArea, field) {
  5544. return scale.options.clip ? scale[field] : chartArea[field];
  5545. }
  5546. function getDatasetArea(meta, chartArea) {
  5547. const { xScale , yScale } = meta;
  5548. if (xScale && yScale) {
  5549. return {
  5550. left: getSizeForArea(xScale, chartArea, 'left'),
  5551. right: getSizeForArea(xScale, chartArea, 'right'),
  5552. top: getSizeForArea(yScale, chartArea, 'top'),
  5553. bottom: getSizeForArea(yScale, chartArea, 'bottom')
  5554. };
  5555. }
  5556. return chartArea;
  5557. }
  5558. class Chart {
  5559. static defaults = defaults;
  5560. static instances = instances;
  5561. static overrides = overrides;
  5562. static registry = registry;
  5563. static version = version;
  5564. static getChart = getChart;
  5565. static register(...items) {
  5566. registry.add(...items);
  5567. invalidatePlugins();
  5568. }
  5569. static unregister(...items) {
  5570. registry.remove(...items);
  5571. invalidatePlugins();
  5572. }
  5573. constructor(item, userConfig){
  5574. const config = this.config = new Config(userConfig);
  5575. const initialCanvas = getCanvas(item);
  5576. const existingChart = getChart(initialCanvas);
  5577. if (existingChart) {
  5578. throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
  5579. }
  5580. const options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5581. this.platform = new (config.platform || _detectPlatform(initialCanvas))();
  5582. this.platform.updateConfig(config);
  5583. const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
  5584. const canvas = context && context.canvas;
  5585. const height = canvas && canvas.height;
  5586. const width = canvas && canvas.width;
  5587. this.id = uid();
  5588. this.ctx = context;
  5589. this.canvas = canvas;
  5590. this.width = width;
  5591. this.height = height;
  5592. this._options = options;
  5593. this._aspectRatio = this.aspectRatio;
  5594. this._layers = [];
  5595. this._metasets = [];
  5596. this._stacks = undefined;
  5597. this.boxes = [];
  5598. this.currentDevicePixelRatio = undefined;
  5599. this.chartArea = undefined;
  5600. this._active = [];
  5601. this._lastEvent = undefined;
  5602. this._listeners = {};
  5603. this._responsiveListeners = undefined;
  5604. this._sortedMetasets = [];
  5605. this.scales = {};
  5606. this._plugins = new PluginService();
  5607. this.$proxies = {};
  5608. this._hiddenIndices = {};
  5609. this.attached = false;
  5610. this._animationsDisabled = undefined;
  5611. this.$context = undefined;
  5612. this._doResize = debounce((mode)=>this.update(mode), options.resizeDelay || 0);
  5613. this._dataChanges = [];
  5614. instances[this.id] = this;
  5615. if (!context || !canvas) {
  5616. console.error("Failed to create chart: can't acquire context from the given item");
  5617. return;
  5618. }
  5619. animator.listen(this, 'complete', onAnimationsComplete);
  5620. animator.listen(this, 'progress', onAnimationProgress);
  5621. this._initialize();
  5622. if (this.attached) {
  5623. this.update();
  5624. }
  5625. }
  5626. get aspectRatio() {
  5627. const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
  5628. if (!isNullOrUndef(aspectRatio)) {
  5629. return aspectRatio;
  5630. }
  5631. if (maintainAspectRatio && _aspectRatio) {
  5632. return _aspectRatio;
  5633. }
  5634. return height ? width / height : null;
  5635. }
  5636. get data() {
  5637. return this.config.data;
  5638. }
  5639. set data(data) {
  5640. this.config.data = data;
  5641. }
  5642. get options() {
  5643. return this._options;
  5644. }
  5645. set options(options) {
  5646. this.config.options = options;
  5647. }
  5648. get registry() {
  5649. return registry;
  5650. }
  5651. _initialize() {
  5652. this.notifyPlugins('beforeInit');
  5653. if (this.options.responsive) {
  5654. this.resize();
  5655. } else {
  5656. retinaScale(this, this.options.devicePixelRatio);
  5657. }
  5658. this.bindEvents();
  5659. this.notifyPlugins('afterInit');
  5660. return this;
  5661. }
  5662. clear() {
  5663. clearCanvas(this.canvas, this.ctx);
  5664. return this;
  5665. }
  5666. stop() {
  5667. animator.stop(this);
  5668. return this;
  5669. }
  5670. resize(width, height) {
  5671. if (!animator.running(this)) {
  5672. this._resize(width, height);
  5673. } else {
  5674. this._resizeBeforeDraw = {
  5675. width,
  5676. height
  5677. };
  5678. }
  5679. }
  5680. _resize(width, height) {
  5681. const options = this.options;
  5682. const canvas = this.canvas;
  5683. const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
  5684. const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
  5685. const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
  5686. const mode = this.width ? 'resize' : 'attach';
  5687. this.width = newSize.width;
  5688. this.height = newSize.height;
  5689. this._aspectRatio = this.aspectRatio;
  5690. if (!retinaScale(this, newRatio, true)) {
  5691. return;
  5692. }
  5693. this.notifyPlugins('resize', {
  5694. size: newSize
  5695. });
  5696. callback(options.onResize, [
  5697. this,
  5698. newSize
  5699. ], this);
  5700. if (this.attached) {
  5701. if (this._doResize(mode)) {
  5702. this.render();
  5703. }
  5704. }
  5705. }
  5706. ensureScalesHaveIDs() {
  5707. const options = this.options;
  5708. const scalesOptions = options.scales || {};
  5709. each(scalesOptions, (axisOptions, axisID)=>{
  5710. axisOptions.id = axisID;
  5711. });
  5712. }
  5713. buildOrUpdateScales() {
  5714. const options = this.options;
  5715. const scaleOpts = options.scales;
  5716. const scales = this.scales;
  5717. const updated = Object.keys(scales).reduce((obj, id)=>{
  5718. obj[id] = false;
  5719. return obj;
  5720. }, {});
  5721. let items = [];
  5722. if (scaleOpts) {
  5723. items = items.concat(Object.keys(scaleOpts).map((id)=>{
  5724. const scaleOptions = scaleOpts[id];
  5725. const axis = determineAxis(id, scaleOptions);
  5726. const isRadial = axis === 'r';
  5727. const isHorizontal = axis === 'x';
  5728. return {
  5729. options: scaleOptions,
  5730. dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
  5731. dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
  5732. };
  5733. }));
  5734. }
  5735. each(items, (item)=>{
  5736. const scaleOptions = item.options;
  5737. const id = scaleOptions.id;
  5738. const axis = determineAxis(id, scaleOptions);
  5739. const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
  5740. if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
  5741. scaleOptions.position = item.dposition;
  5742. }
  5743. updated[id] = true;
  5744. let scale = null;
  5745. if (id in scales && scales[id].type === scaleType) {
  5746. scale = scales[id];
  5747. } else {
  5748. const scaleClass = registry.getScale(scaleType);
  5749. scale = new scaleClass({
  5750. id,
  5751. type: scaleType,
  5752. ctx: this.ctx,
  5753. chart: this
  5754. });
  5755. scales[scale.id] = scale;
  5756. }
  5757. scale.init(scaleOptions, options);
  5758. });
  5759. each(updated, (hasUpdated, id)=>{
  5760. if (!hasUpdated) {
  5761. delete scales[id];
  5762. }
  5763. });
  5764. each(scales, (scale)=>{
  5765. layouts.configure(this, scale, scale.options);
  5766. layouts.addBox(this, scale);
  5767. });
  5768. }
  5769. _updateMetasets() {
  5770. const metasets = this._metasets;
  5771. const numData = this.data.datasets.length;
  5772. const numMeta = metasets.length;
  5773. metasets.sort((a, b)=>a.index - b.index);
  5774. if (numMeta > numData) {
  5775. for(let i = numData; i < numMeta; ++i){
  5776. this._destroyDatasetMeta(i);
  5777. }
  5778. metasets.splice(numData, numMeta - numData);
  5779. }
  5780. this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
  5781. }
  5782. _removeUnreferencedMetasets() {
  5783. const { _metasets: metasets , data: { datasets } } = this;
  5784. if (metasets.length > datasets.length) {
  5785. delete this._stacks;
  5786. }
  5787. metasets.forEach((meta, index)=>{
  5788. if (datasets.filter((x)=>x === meta._dataset).length === 0) {
  5789. this._destroyDatasetMeta(index);
  5790. }
  5791. });
  5792. }
  5793. buildOrUpdateControllers() {
  5794. const newControllers = [];
  5795. const datasets = this.data.datasets;
  5796. let i, ilen;
  5797. this._removeUnreferencedMetasets();
  5798. for(i = 0, ilen = datasets.length; i < ilen; i++){
  5799. const dataset = datasets[i];
  5800. let meta = this.getDatasetMeta(i);
  5801. const type = dataset.type || this.config.type;
  5802. if (meta.type && meta.type !== type) {
  5803. this._destroyDatasetMeta(i);
  5804. meta = this.getDatasetMeta(i);
  5805. }
  5806. meta.type = type;
  5807. meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
  5808. meta.order = dataset.order || 0;
  5809. meta.index = i;
  5810. meta.label = '' + dataset.label;
  5811. meta.visible = this.isDatasetVisible(i);
  5812. if (meta.controller) {
  5813. meta.controller.updateIndex(i);
  5814. meta.controller.linkScales();
  5815. } else {
  5816. const ControllerClass = registry.getController(type);
  5817. const { datasetElementType , dataElementType } = defaults.datasets[type];
  5818. Object.assign(ControllerClass, {
  5819. dataElementType: registry.getElement(dataElementType),
  5820. datasetElementType: datasetElementType && registry.getElement(datasetElementType)
  5821. });
  5822. meta.controller = new ControllerClass(this, i);
  5823. newControllers.push(meta.controller);
  5824. }
  5825. }
  5826. this._updateMetasets();
  5827. return newControllers;
  5828. }
  5829. _resetElements() {
  5830. each(this.data.datasets, (dataset, datasetIndex)=>{
  5831. this.getDatasetMeta(datasetIndex).controller.reset();
  5832. }, this);
  5833. }
  5834. reset() {
  5835. this._resetElements();
  5836. this.notifyPlugins('reset');
  5837. }
  5838. update(mode) {
  5839. const config = this.config;
  5840. config.update();
  5841. const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5842. const animsDisabled = this._animationsDisabled = !options.animation;
  5843. this._updateScales();
  5844. this._checkEventBindings();
  5845. this._updateHiddenIndices();
  5846. this._plugins.invalidate();
  5847. if (this.notifyPlugins('beforeUpdate', {
  5848. mode,
  5849. cancelable: true
  5850. }) === false) {
  5851. return;
  5852. }
  5853. const newControllers = this.buildOrUpdateControllers();
  5854. this.notifyPlugins('beforeElementsUpdate');
  5855. let minPadding = 0;
  5856. for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
  5857. const { controller } = this.getDatasetMeta(i);
  5858. const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
  5859. controller.buildOrUpdateElements(reset);
  5860. minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
  5861. }
  5862. minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
  5863. this._updateLayout(minPadding);
  5864. if (!animsDisabled) {
  5865. each(newControllers, (controller)=>{
  5866. controller.reset();
  5867. });
  5868. }
  5869. this._updateDatasets(mode);
  5870. this.notifyPlugins('afterUpdate', {
  5871. mode
  5872. });
  5873. this._layers.sort(compare2Level('z', '_idx'));
  5874. const { _active , _lastEvent } = this;
  5875. if (_lastEvent) {
  5876. this._eventHandler(_lastEvent, true);
  5877. } else if (_active.length) {
  5878. this._updateHoverStyles(_active, _active, true);
  5879. }
  5880. this.render();
  5881. }
  5882. _updateScales() {
  5883. each(this.scales, (scale)=>{
  5884. layouts.removeBox(this, scale);
  5885. });
  5886. this.ensureScalesHaveIDs();
  5887. this.buildOrUpdateScales();
  5888. }
  5889. _checkEventBindings() {
  5890. const options = this.options;
  5891. const existingEvents = new Set(Object.keys(this._listeners));
  5892. const newEvents = new Set(options.events);
  5893. if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
  5894. this.unbindEvents();
  5895. this.bindEvents();
  5896. }
  5897. }
  5898. _updateHiddenIndices() {
  5899. const { _hiddenIndices } = this;
  5900. const changes = this._getUniformDataChanges() || [];
  5901. for (const { method , start , count } of changes){
  5902. const move = method === '_removeElements' ? -count : count;
  5903. moveNumericKeys(_hiddenIndices, start, move);
  5904. }
  5905. }
  5906. _getUniformDataChanges() {
  5907. const _dataChanges = this._dataChanges;
  5908. if (!_dataChanges || !_dataChanges.length) {
  5909. return;
  5910. }
  5911. this._dataChanges = [];
  5912. const datasetCount = this.data.datasets.length;
  5913. const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
  5914. const changeSet = makeSet(0);
  5915. for(let i = 1; i < datasetCount; i++){
  5916. if (!setsEqual(changeSet, makeSet(i))) {
  5917. return;
  5918. }
  5919. }
  5920. return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
  5921. method: a[1],
  5922. start: +a[2],
  5923. count: +a[3]
  5924. }));
  5925. }
  5926. _updateLayout(minPadding) {
  5927. if (this.notifyPlugins('beforeLayout', {
  5928. cancelable: true
  5929. }) === false) {
  5930. return;
  5931. }
  5932. layouts.update(this, this.width, this.height, minPadding);
  5933. const area = this.chartArea;
  5934. const noArea = area.width <= 0 || area.height <= 0;
  5935. this._layers = [];
  5936. each(this.boxes, (box)=>{
  5937. if (noArea && box.position === 'chartArea') {
  5938. return;
  5939. }
  5940. if (box.configure) {
  5941. box.configure();
  5942. }
  5943. this._layers.push(...box._layers());
  5944. }, this);
  5945. this._layers.forEach((item, index)=>{
  5946. item._idx = index;
  5947. });
  5948. this.notifyPlugins('afterLayout');
  5949. }
  5950. _updateDatasets(mode) {
  5951. if (this.notifyPlugins('beforeDatasetsUpdate', {
  5952. mode,
  5953. cancelable: true
  5954. }) === false) {
  5955. return;
  5956. }
  5957. for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  5958. this.getDatasetMeta(i).controller.configure();
  5959. }
  5960. for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  5961. this._updateDataset(i, isFunction(mode) ? mode({
  5962. datasetIndex: i
  5963. }) : mode);
  5964. }
  5965. this.notifyPlugins('afterDatasetsUpdate', {
  5966. mode
  5967. });
  5968. }
  5969. _updateDataset(index, mode) {
  5970. const meta = this.getDatasetMeta(index);
  5971. const args = {
  5972. meta,
  5973. index,
  5974. mode,
  5975. cancelable: true
  5976. };
  5977. if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
  5978. return;
  5979. }
  5980. meta.controller._update(mode);
  5981. args.cancelable = false;
  5982. this.notifyPlugins('afterDatasetUpdate', args);
  5983. }
  5984. render() {
  5985. if (this.notifyPlugins('beforeRender', {
  5986. cancelable: true
  5987. }) === false) {
  5988. return;
  5989. }
  5990. if (animator.has(this)) {
  5991. if (this.attached && !animator.running(this)) {
  5992. animator.start(this);
  5993. }
  5994. } else {
  5995. this.draw();
  5996. onAnimationsComplete({
  5997. chart: this
  5998. });
  5999. }
  6000. }
  6001. draw() {
  6002. let i;
  6003. if (this._resizeBeforeDraw) {
  6004. const { width , height } = this._resizeBeforeDraw;
  6005. this._resize(width, height);
  6006. this._resizeBeforeDraw = null;
  6007. }
  6008. this.clear();
  6009. if (this.width <= 0 || this.height <= 0) {
  6010. return;
  6011. }
  6012. if (this.notifyPlugins('beforeDraw', {
  6013. cancelable: true
  6014. }) === false) {
  6015. return;
  6016. }
  6017. const layers = this._layers;
  6018. for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
  6019. layers[i].draw(this.chartArea);
  6020. }
  6021. this._drawDatasets();
  6022. for(; i < layers.length; ++i){
  6023. layers[i].draw(this.chartArea);
  6024. }
  6025. this.notifyPlugins('afterDraw');
  6026. }
  6027. _getSortedDatasetMetas(filterVisible) {
  6028. const metasets = this._sortedMetasets;
  6029. const result = [];
  6030. let i, ilen;
  6031. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  6032. const meta = metasets[i];
  6033. if (!filterVisible || meta.visible) {
  6034. result.push(meta);
  6035. }
  6036. }
  6037. return result;
  6038. }
  6039. getSortedVisibleDatasetMetas() {
  6040. return this._getSortedDatasetMetas(true);
  6041. }
  6042. _drawDatasets() {
  6043. if (this.notifyPlugins('beforeDatasetsDraw', {
  6044. cancelable: true
  6045. }) === false) {
  6046. return;
  6047. }
  6048. const metasets = this.getSortedVisibleDatasetMetas();
  6049. for(let i = metasets.length - 1; i >= 0; --i){
  6050. this._drawDataset(metasets[i]);
  6051. }
  6052. this.notifyPlugins('afterDatasetsDraw');
  6053. }
  6054. _drawDataset(meta) {
  6055. const ctx = this.ctx;
  6056. const clip = meta._clip;
  6057. const useClip = !clip.disabled;
  6058. const area = getDatasetArea(meta, this.chartArea);
  6059. const args = {
  6060. meta,
  6061. index: meta.index,
  6062. cancelable: true
  6063. };
  6064. if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
  6065. return;
  6066. }
  6067. if (useClip) {
  6068. clipArea(ctx, {
  6069. left: clip.left === false ? 0 : area.left - clip.left,
  6070. right: clip.right === false ? this.width : area.right + clip.right,
  6071. top: clip.top === false ? 0 : area.top - clip.top,
  6072. bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
  6073. });
  6074. }
  6075. meta.controller.draw();
  6076. if (useClip) {
  6077. unclipArea(ctx);
  6078. }
  6079. args.cancelable = false;
  6080. this.notifyPlugins('afterDatasetDraw', args);
  6081. }
  6082. isPointInArea(point) {
  6083. return _isPointInArea(point, this.chartArea, this._minPadding);
  6084. }
  6085. getElementsAtEventForMode(e, mode, options, useFinalPosition) {
  6086. const method = Interaction.modes[mode];
  6087. if (typeof method === 'function') {
  6088. return method(this, e, options, useFinalPosition);
  6089. }
  6090. return [];
  6091. }
  6092. getDatasetMeta(datasetIndex) {
  6093. const dataset = this.data.datasets[datasetIndex];
  6094. const metasets = this._metasets;
  6095. let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
  6096. if (!meta) {
  6097. meta = {
  6098. type: null,
  6099. data: [],
  6100. dataset: null,
  6101. controller: null,
  6102. hidden: null,
  6103. xAxisID: null,
  6104. yAxisID: null,
  6105. order: dataset && dataset.order || 0,
  6106. index: datasetIndex,
  6107. _dataset: dataset,
  6108. _parsed: [],
  6109. _sorted: false
  6110. };
  6111. metasets.push(meta);
  6112. }
  6113. return meta;
  6114. }
  6115. getContext() {
  6116. return this.$context || (this.$context = createContext(null, {
  6117. chart: this,
  6118. type: 'chart'
  6119. }));
  6120. }
  6121. getVisibleDatasetCount() {
  6122. return this.getSortedVisibleDatasetMetas().length;
  6123. }
  6124. isDatasetVisible(datasetIndex) {
  6125. const dataset = this.data.datasets[datasetIndex];
  6126. if (!dataset) {
  6127. return false;
  6128. }
  6129. const meta = this.getDatasetMeta(datasetIndex);
  6130. return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
  6131. }
  6132. setDatasetVisibility(datasetIndex, visible) {
  6133. const meta = this.getDatasetMeta(datasetIndex);
  6134. meta.hidden = !visible;
  6135. }
  6136. toggleDataVisibility(index) {
  6137. this._hiddenIndices[index] = !this._hiddenIndices[index];
  6138. }
  6139. getDataVisibility(index) {
  6140. return !this._hiddenIndices[index];
  6141. }
  6142. _updateVisibility(datasetIndex, dataIndex, visible) {
  6143. const mode = visible ? 'show' : 'hide';
  6144. const meta = this.getDatasetMeta(datasetIndex);
  6145. const anims = meta.controller._resolveAnimations(undefined, mode);
  6146. if (defined(dataIndex)) {
  6147. meta.data[dataIndex].hidden = !visible;
  6148. this.update();
  6149. } else {
  6150. this.setDatasetVisibility(datasetIndex, visible);
  6151. anims.update(meta, {
  6152. visible
  6153. });
  6154. this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
  6155. }
  6156. }
  6157. hide(datasetIndex, dataIndex) {
  6158. this._updateVisibility(datasetIndex, dataIndex, false);
  6159. }
  6160. show(datasetIndex, dataIndex) {
  6161. this._updateVisibility(datasetIndex, dataIndex, true);
  6162. }
  6163. _destroyDatasetMeta(datasetIndex) {
  6164. const meta = this._metasets[datasetIndex];
  6165. if (meta && meta.controller) {
  6166. meta.controller._destroy();
  6167. }
  6168. delete this._metasets[datasetIndex];
  6169. }
  6170. _stop() {
  6171. let i, ilen;
  6172. this.stop();
  6173. animator.remove(this);
  6174. for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  6175. this._destroyDatasetMeta(i);
  6176. }
  6177. }
  6178. destroy() {
  6179. this.notifyPlugins('beforeDestroy');
  6180. const { canvas , ctx } = this;
  6181. this._stop();
  6182. this.config.clearCache();
  6183. if (canvas) {
  6184. this.unbindEvents();
  6185. clearCanvas(canvas, ctx);
  6186. this.platform.releaseContext(ctx);
  6187. this.canvas = null;
  6188. this.ctx = null;
  6189. }
  6190. delete instances[this.id];
  6191. this.notifyPlugins('afterDestroy');
  6192. }
  6193. toBase64Image(...args) {
  6194. return this.canvas.toDataURL(...args);
  6195. }
  6196. bindEvents() {
  6197. this.bindUserEvents();
  6198. if (this.options.responsive) {
  6199. this.bindResponsiveEvents();
  6200. } else {
  6201. this.attached = true;
  6202. }
  6203. }
  6204. bindUserEvents() {
  6205. const listeners = this._listeners;
  6206. const platform = this.platform;
  6207. const _add = (type, listener)=>{
  6208. platform.addEventListener(this, type, listener);
  6209. listeners[type] = listener;
  6210. };
  6211. const listener = (e, x, y)=>{
  6212. e.offsetX = x;
  6213. e.offsetY = y;
  6214. this._eventHandler(e);
  6215. };
  6216. each(this.options.events, (type)=>_add(type, listener));
  6217. }
  6218. bindResponsiveEvents() {
  6219. if (!this._responsiveListeners) {
  6220. this._responsiveListeners = {};
  6221. }
  6222. const listeners = this._responsiveListeners;
  6223. const platform = this.platform;
  6224. const _add = (type, listener)=>{
  6225. platform.addEventListener(this, type, listener);
  6226. listeners[type] = listener;
  6227. };
  6228. const _remove = (type, listener)=>{
  6229. if (listeners[type]) {
  6230. platform.removeEventListener(this, type, listener);
  6231. delete listeners[type];
  6232. }
  6233. };
  6234. const listener = (width, height)=>{
  6235. if (this.canvas) {
  6236. this.resize(width, height);
  6237. }
  6238. };
  6239. let detached;
  6240. const attached = ()=>{
  6241. _remove('attach', attached);
  6242. this.attached = true;
  6243. this.resize();
  6244. _add('resize', listener);
  6245. _add('detach', detached);
  6246. };
  6247. detached = ()=>{
  6248. this.attached = false;
  6249. _remove('resize', listener);
  6250. this._stop();
  6251. this._resize(0, 0);
  6252. _add('attach', attached);
  6253. };
  6254. if (platform.isAttached(this.canvas)) {
  6255. attached();
  6256. } else {
  6257. detached();
  6258. }
  6259. }
  6260. unbindEvents() {
  6261. each(this._listeners, (listener, type)=>{
  6262. this.platform.removeEventListener(this, type, listener);
  6263. });
  6264. this._listeners = {};
  6265. each(this._responsiveListeners, (listener, type)=>{
  6266. this.platform.removeEventListener(this, type, listener);
  6267. });
  6268. this._responsiveListeners = undefined;
  6269. }
  6270. updateHoverStyle(items, mode, enabled) {
  6271. const prefix = enabled ? 'set' : 'remove';
  6272. let meta, item, i, ilen;
  6273. if (mode === 'dataset') {
  6274. meta = this.getDatasetMeta(items[0].datasetIndex);
  6275. meta.controller['_' + prefix + 'DatasetHoverStyle']();
  6276. }
  6277. for(i = 0, ilen = items.length; i < ilen; ++i){
  6278. item = items[i];
  6279. const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
  6280. if (controller) {
  6281. controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
  6282. }
  6283. }
  6284. }
  6285. getActiveElements() {
  6286. return this._active || [];
  6287. }
  6288. setActiveElements(activeElements) {
  6289. const lastActive = this._active || [];
  6290. const active = activeElements.map(({ datasetIndex , index })=>{
  6291. const meta = this.getDatasetMeta(datasetIndex);
  6292. if (!meta) {
  6293. throw new Error('No dataset found at index ' + datasetIndex);
  6294. }
  6295. return {
  6296. datasetIndex,
  6297. element: meta.data[index],
  6298. index
  6299. };
  6300. });
  6301. const changed = !_elementsEqual(active, lastActive);
  6302. if (changed) {
  6303. this._active = active;
  6304. this._lastEvent = null;
  6305. this._updateHoverStyles(active, lastActive);
  6306. }
  6307. }
  6308. notifyPlugins(hook, args, filter) {
  6309. return this._plugins.notify(this, hook, args, filter);
  6310. }
  6311. isPluginEnabled(pluginId) {
  6312. return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
  6313. }
  6314. _updateHoverStyles(active, lastActive, replay) {
  6315. const hoverOptions = this.options.hover;
  6316. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
  6317. const deactivated = diff(lastActive, active);
  6318. const activated = replay ? active : diff(active, lastActive);
  6319. if (deactivated.length) {
  6320. this.updateHoverStyle(deactivated, hoverOptions.mode, false);
  6321. }
  6322. if (activated.length && hoverOptions.mode) {
  6323. this.updateHoverStyle(activated, hoverOptions.mode, true);
  6324. }
  6325. }
  6326. _eventHandler(e, replay) {
  6327. const args = {
  6328. event: e,
  6329. replay,
  6330. cancelable: true,
  6331. inChartArea: this.isPointInArea(e)
  6332. };
  6333. const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
  6334. if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
  6335. return;
  6336. }
  6337. const changed = this._handleEvent(e, replay, args.inChartArea);
  6338. args.cancelable = false;
  6339. this.notifyPlugins('afterEvent', args, eventFilter);
  6340. if (changed || args.changed) {
  6341. this.render();
  6342. }
  6343. return this;
  6344. }
  6345. _handleEvent(e, replay, inChartArea) {
  6346. const { _active: lastActive = [] , options } = this;
  6347. const useFinalPosition = replay;
  6348. const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
  6349. const isClick = _isClickEvent(e);
  6350. const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
  6351. if (inChartArea) {
  6352. this._lastEvent = null;
  6353. callback(options.onHover, [
  6354. e,
  6355. active,
  6356. this
  6357. ], this);
  6358. if (isClick) {
  6359. callback(options.onClick, [
  6360. e,
  6361. active,
  6362. this
  6363. ], this);
  6364. }
  6365. }
  6366. const changed = !_elementsEqual(active, lastActive);
  6367. if (changed || replay) {
  6368. this._active = active;
  6369. this._updateHoverStyles(active, lastActive, replay);
  6370. }
  6371. this._lastEvent = lastEvent;
  6372. return changed;
  6373. }
  6374. _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
  6375. if (e.type === 'mouseout') {
  6376. return [];
  6377. }
  6378. if (!inChartArea) {
  6379. return lastActive;
  6380. }
  6381. const hoverOptions = this.options.hover;
  6382. return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
  6383. }
  6384. }
  6385. function invalidatePlugins() {
  6386. return each(Chart.instances, (chart)=>chart._plugins.invalidate());
  6387. }
  6388. function clipArc(ctx, element, endAngle) {
  6389. const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
  6390. let angleMargin = pixelMargin / outerRadius;
  6391. // Draw an inner border by clipping the arc and drawing a double-width border
  6392. // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
  6393. ctx.beginPath();
  6394. ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
  6395. if (innerRadius > pixelMargin) {
  6396. angleMargin = pixelMargin / innerRadius;
  6397. ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
  6398. } else {
  6399. ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
  6400. }
  6401. ctx.closePath();
  6402. ctx.clip();
  6403. }
  6404. function toRadiusCorners(value) {
  6405. return _readValueToProps(value, [
  6406. 'outerStart',
  6407. 'outerEnd',
  6408. 'innerStart',
  6409. 'innerEnd'
  6410. ]);
  6411. }
  6412. /**
  6413. * Parse border radius from the provided options
  6414. */ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
  6415. const o = toRadiusCorners(arc.options.borderRadius);
  6416. const halfThickness = (outerRadius - innerRadius) / 2;
  6417. const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
  6418. // Outer limits are complicated. We want to compute the available angular distance at
  6419. // a radius of outerRadius - borderRadius because for small angular distances, this term limits.
  6420. // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
  6421. //
  6422. // If the borderRadius is large, that value can become negative.
  6423. // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
  6424. // we know that the thickness term will dominate and compute the limits at that point
  6425. const computeOuterLimit = (val)=>{
  6426. const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
  6427. return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
  6428. };
  6429. return {
  6430. outerStart: computeOuterLimit(o.outerStart),
  6431. outerEnd: computeOuterLimit(o.outerEnd),
  6432. innerStart: _limitValue(o.innerStart, 0, innerLimit),
  6433. innerEnd: _limitValue(o.innerEnd, 0, innerLimit)
  6434. };
  6435. }
  6436. /**
  6437. * Convert (r, 𝜃) to (x, y)
  6438. */ function rThetaToXY(r, theta, x, y) {
  6439. return {
  6440. x: x + r * Math.cos(theta),
  6441. y: y + r * Math.sin(theta)
  6442. };
  6443. }
  6444. /**
  6445. * Path the arc, respecting border radius by separating into left and right halves.
  6446. *
  6447. * Start End
  6448. *
  6449. * 1--->a--->2 Outer
  6450. * / \
  6451. * 8 3
  6452. * | |
  6453. * | |
  6454. * 7 4
  6455. * \ /
  6456. * 6<---b<---5 Inner
  6457. */ function pathArc(ctx, element, offset, spacing, end, circular) {
  6458. const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
  6459. const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
  6460. const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
  6461. let spacingOffset = 0;
  6462. const alpha = end - start;
  6463. if (spacing) {
  6464. // When spacing is present, it is the same for all items
  6465. // So we adjust the start and end angle of the arc such that
  6466. // the distance is the same as it would be without the spacing
  6467. const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
  6468. const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
  6469. const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
  6470. const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
  6471. spacingOffset = (alpha - adjustedAngle) / 2;
  6472. }
  6473. const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
  6474. const angleOffset = (alpha - beta) / 2;
  6475. const startAngle = start + angleOffset + spacingOffset;
  6476. const endAngle = end - angleOffset - spacingOffset;
  6477. const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
  6478. const outerStartAdjustedRadius = outerRadius - outerStart;
  6479. const outerEndAdjustedRadius = outerRadius - outerEnd;
  6480. const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
  6481. const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
  6482. const innerStartAdjustedRadius = innerRadius + innerStart;
  6483. const innerEndAdjustedRadius = innerRadius + innerEnd;
  6484. const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
  6485. const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
  6486. ctx.beginPath();
  6487. if (circular) {
  6488. // The first arc segments from point 1 to point a to point 2
  6489. const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
  6490. ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
  6491. ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
  6492. // The corner segment from point 2 to point 3
  6493. if (outerEnd > 0) {
  6494. const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
  6495. ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
  6496. }
  6497. // The line from point 3 to point 4
  6498. const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
  6499. ctx.lineTo(p4.x, p4.y);
  6500. // The corner segment from point 4 to point 5
  6501. if (innerEnd > 0) {
  6502. const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
  6503. ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
  6504. }
  6505. // The inner arc from point 5 to point b to point 6
  6506. const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
  6507. ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
  6508. ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
  6509. // The corner segment from point 6 to point 7
  6510. if (innerStart > 0) {
  6511. const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
  6512. ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
  6513. }
  6514. // The line from point 7 to point 8
  6515. const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
  6516. ctx.lineTo(p8.x, p8.y);
  6517. // The corner segment from point 8 to point 1
  6518. if (outerStart > 0) {
  6519. const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
  6520. ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
  6521. }
  6522. } else {
  6523. ctx.moveTo(x, y);
  6524. const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
  6525. const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
  6526. ctx.lineTo(outerStartX, outerStartY);
  6527. const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
  6528. const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
  6529. ctx.lineTo(outerEndX, outerEndY);
  6530. }
  6531. ctx.closePath();
  6532. }
  6533. function drawArc(ctx, element, offset, spacing, circular) {
  6534. const { fullCircles , startAngle , circumference } = element;
  6535. let endAngle = element.endAngle;
  6536. if (fullCircles) {
  6537. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6538. for(let i = 0; i < fullCircles; ++i){
  6539. ctx.fill();
  6540. }
  6541. if (!isNaN(circumference)) {
  6542. endAngle = startAngle + (circumference % TAU || TAU);
  6543. }
  6544. }
  6545. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6546. ctx.fill();
  6547. return endAngle;
  6548. }
  6549. function drawBorder(ctx, element, offset, spacing, circular) {
  6550. const { fullCircles , startAngle , circumference , options } = element;
  6551. const { borderWidth , borderJoinStyle , borderDash , borderDashOffset } = options;
  6552. const inner = options.borderAlign === 'inner';
  6553. if (!borderWidth) {
  6554. return;
  6555. }
  6556. ctx.setLineDash(borderDash || []);
  6557. ctx.lineDashOffset = borderDashOffset;
  6558. if (inner) {
  6559. ctx.lineWidth = borderWidth * 2;
  6560. ctx.lineJoin = borderJoinStyle || 'round';
  6561. } else {
  6562. ctx.lineWidth = borderWidth;
  6563. ctx.lineJoin = borderJoinStyle || 'bevel';
  6564. }
  6565. let endAngle = element.endAngle;
  6566. if (fullCircles) {
  6567. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6568. for(let i = 0; i < fullCircles; ++i){
  6569. ctx.stroke();
  6570. }
  6571. if (!isNaN(circumference)) {
  6572. endAngle = startAngle + (circumference % TAU || TAU);
  6573. }
  6574. }
  6575. if (inner) {
  6576. clipArc(ctx, element, endAngle);
  6577. }
  6578. if (!fullCircles) {
  6579. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6580. ctx.stroke();
  6581. }
  6582. }
  6583. class ArcElement extends Element {
  6584. static id = 'arc';
  6585. static defaults = {
  6586. borderAlign: 'center',
  6587. borderColor: '#fff',
  6588. borderDash: [],
  6589. borderDashOffset: 0,
  6590. borderJoinStyle: undefined,
  6591. borderRadius: 0,
  6592. borderWidth: 2,
  6593. offset: 0,
  6594. spacing: 0,
  6595. angle: undefined,
  6596. circular: true
  6597. };
  6598. static defaultRoutes = {
  6599. backgroundColor: 'backgroundColor'
  6600. };
  6601. static descriptors = {
  6602. _scriptable: true,
  6603. _indexable: (name)=>name !== 'borderDash'
  6604. };
  6605. circumference;
  6606. endAngle;
  6607. fullCircles;
  6608. innerRadius;
  6609. outerRadius;
  6610. pixelMargin;
  6611. startAngle;
  6612. constructor(cfg){
  6613. super();
  6614. this.options = undefined;
  6615. this.circumference = undefined;
  6616. this.startAngle = undefined;
  6617. this.endAngle = undefined;
  6618. this.innerRadius = undefined;
  6619. this.outerRadius = undefined;
  6620. this.pixelMargin = 0;
  6621. this.fullCircles = 0;
  6622. if (cfg) {
  6623. Object.assign(this, cfg);
  6624. }
  6625. }
  6626. inRange(chartX, chartY, useFinalPosition) {
  6627. const point = this.getProps([
  6628. 'x',
  6629. 'y'
  6630. ], useFinalPosition);
  6631. const { angle , distance } = getAngleFromPoint(point, {
  6632. x: chartX,
  6633. y: chartY
  6634. });
  6635. const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
  6636. 'startAngle',
  6637. 'endAngle',
  6638. 'innerRadius',
  6639. 'outerRadius',
  6640. 'circumference'
  6641. ], useFinalPosition);
  6642. const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
  6643. const _circumference = valueOrDefault(circumference, endAngle - startAngle);
  6644. const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
  6645. const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
  6646. return betweenAngles && withinRadius;
  6647. }
  6648. getCenterPoint(useFinalPosition) {
  6649. const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
  6650. 'x',
  6651. 'y',
  6652. 'startAngle',
  6653. 'endAngle',
  6654. 'innerRadius',
  6655. 'outerRadius'
  6656. ], useFinalPosition);
  6657. const { offset , spacing } = this.options;
  6658. const halfAngle = (startAngle + endAngle) / 2;
  6659. const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
  6660. return {
  6661. x: x + Math.cos(halfAngle) * halfRadius,
  6662. y: y + Math.sin(halfAngle) * halfRadius
  6663. };
  6664. }
  6665. tooltipPosition(useFinalPosition) {
  6666. return this.getCenterPoint(useFinalPosition);
  6667. }
  6668. draw(ctx) {
  6669. const { options , circumference } = this;
  6670. const offset = (options.offset || 0) / 4;
  6671. const spacing = (options.spacing || 0) / 2;
  6672. const circular = options.circular;
  6673. this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
  6674. this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
  6675. if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
  6676. return;
  6677. }
  6678. ctx.save();
  6679. const halfAngle = (this.startAngle + this.endAngle) / 2;
  6680. ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
  6681. const fix = 1 - Math.sin(Math.min(PI, circumference || 0));
  6682. const radiusOffset = offset * fix;
  6683. ctx.fillStyle = options.backgroundColor;
  6684. ctx.strokeStyle = options.borderColor;
  6685. drawArc(ctx, this, radiusOffset, spacing, circular);
  6686. drawBorder(ctx, this, radiusOffset, spacing, circular);
  6687. ctx.restore();
  6688. }
  6689. }
  6690. function setStyle(ctx, options, style = options) {
  6691. ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
  6692. ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
  6693. ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
  6694. ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
  6695. ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
  6696. ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
  6697. }
  6698. function lineTo(ctx, previous, target) {
  6699. ctx.lineTo(target.x, target.y);
  6700. }
  6701. function getLineMethod(options) {
  6702. if (options.stepped) {
  6703. return _steppedLineTo;
  6704. }
  6705. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6706. return _bezierCurveTo;
  6707. }
  6708. return lineTo;
  6709. }
  6710. function pathVars(points, segment, params = {}) {
  6711. const count = points.length;
  6712. const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
  6713. const { start: segmentStart , end: segmentEnd } = segment;
  6714. const start = Math.max(paramsStart, segmentStart);
  6715. const end = Math.min(paramsEnd, segmentEnd);
  6716. const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
  6717. return {
  6718. count,
  6719. start,
  6720. loop: segment.loop,
  6721. ilen: end < start && !outside ? count + end - start : end - start
  6722. };
  6723. }
  6724. function pathSegment(ctx, line, segment, params) {
  6725. const { points , options } = line;
  6726. const { count , start , loop , ilen } = pathVars(points, segment, params);
  6727. const lineMethod = getLineMethod(options);
  6728. let { move =true , reverse } = params || {};
  6729. let i, point, prev;
  6730. for(i = 0; i <= ilen; ++i){
  6731. point = points[(start + (reverse ? ilen - i : i)) % count];
  6732. if (point.skip) {
  6733. continue;
  6734. } else if (move) {
  6735. ctx.moveTo(point.x, point.y);
  6736. move = false;
  6737. } else {
  6738. lineMethod(ctx, prev, point, reverse, options.stepped);
  6739. }
  6740. prev = point;
  6741. }
  6742. if (loop) {
  6743. point = points[(start + (reverse ? ilen : 0)) % count];
  6744. lineMethod(ctx, prev, point, reverse, options.stepped);
  6745. }
  6746. return !!loop;
  6747. }
  6748. function fastPathSegment(ctx, line, segment, params) {
  6749. const points = line.points;
  6750. const { count , start , ilen } = pathVars(points, segment, params);
  6751. const { move =true , reverse } = params || {};
  6752. let avgX = 0;
  6753. let countX = 0;
  6754. let i, point, prevX, minY, maxY, lastY;
  6755. const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
  6756. const drawX = ()=>{
  6757. if (minY !== maxY) {
  6758. ctx.lineTo(avgX, maxY);
  6759. ctx.lineTo(avgX, minY);
  6760. ctx.lineTo(avgX, lastY);
  6761. }
  6762. };
  6763. if (move) {
  6764. point = points[pointIndex(0)];
  6765. ctx.moveTo(point.x, point.y);
  6766. }
  6767. for(i = 0; i <= ilen; ++i){
  6768. point = points[pointIndex(i)];
  6769. if (point.skip) {
  6770. continue;
  6771. }
  6772. const x = point.x;
  6773. const y = point.y;
  6774. const truncX = x | 0;
  6775. if (truncX === prevX) {
  6776. if (y < minY) {
  6777. minY = y;
  6778. } else if (y > maxY) {
  6779. maxY = y;
  6780. }
  6781. avgX = (countX * avgX + x) / ++countX;
  6782. } else {
  6783. drawX();
  6784. ctx.lineTo(x, y);
  6785. prevX = truncX;
  6786. countX = 0;
  6787. minY = maxY = y;
  6788. }
  6789. lastY = y;
  6790. }
  6791. drawX();
  6792. }
  6793. function _getSegmentMethod(line) {
  6794. const opts = line.options;
  6795. const borderDash = opts.borderDash && opts.borderDash.length;
  6796. const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
  6797. return useFastPath ? fastPathSegment : pathSegment;
  6798. }
  6799. function _getInterpolationMethod(options) {
  6800. if (options.stepped) {
  6801. return _steppedInterpolation;
  6802. }
  6803. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6804. return _bezierInterpolation;
  6805. }
  6806. return _pointInLine;
  6807. }
  6808. function strokePathWithCache(ctx, line, start, count) {
  6809. let path = line._path;
  6810. if (!path) {
  6811. path = line._path = new Path2D();
  6812. if (line.path(path, start, count)) {
  6813. path.closePath();
  6814. }
  6815. }
  6816. setStyle(ctx, line.options);
  6817. ctx.stroke(path);
  6818. }
  6819. function strokePathDirect(ctx, line, start, count) {
  6820. const { segments , options } = line;
  6821. const segmentMethod = _getSegmentMethod(line);
  6822. for (const segment of segments){
  6823. setStyle(ctx, options, segment.style);
  6824. ctx.beginPath();
  6825. if (segmentMethod(ctx, line, segment, {
  6826. start,
  6827. end: start + count - 1
  6828. })) {
  6829. ctx.closePath();
  6830. }
  6831. ctx.stroke();
  6832. }
  6833. }
  6834. const usePath2D = typeof Path2D === 'function';
  6835. function draw(ctx, line, start, count) {
  6836. if (usePath2D && !line.options.segment) {
  6837. strokePathWithCache(ctx, line, start, count);
  6838. } else {
  6839. strokePathDirect(ctx, line, start, count);
  6840. }
  6841. }
  6842. class LineElement extends Element {
  6843. static id = 'line';
  6844. static defaults = {
  6845. borderCapStyle: 'butt',
  6846. borderDash: [],
  6847. borderDashOffset: 0,
  6848. borderJoinStyle: 'miter',
  6849. borderWidth: 3,
  6850. capBezierPoints: true,
  6851. cubicInterpolationMode: 'default',
  6852. fill: false,
  6853. spanGaps: false,
  6854. stepped: false,
  6855. tension: 0
  6856. };
  6857. static defaultRoutes = {
  6858. backgroundColor: 'backgroundColor',
  6859. borderColor: 'borderColor'
  6860. };
  6861. static descriptors = {
  6862. _scriptable: true,
  6863. _indexable: (name)=>name !== 'borderDash' && name !== 'fill'
  6864. };
  6865. constructor(cfg){
  6866. super();
  6867. this.animated = true;
  6868. this.options = undefined;
  6869. this._chart = undefined;
  6870. this._loop = undefined;
  6871. this._fullLoop = undefined;
  6872. this._path = undefined;
  6873. this._points = undefined;
  6874. this._segments = undefined;
  6875. this._decimated = false;
  6876. this._pointsUpdated = false;
  6877. this._datasetIndex = undefined;
  6878. if (cfg) {
  6879. Object.assign(this, cfg);
  6880. }
  6881. }
  6882. updateControlPoints(chartArea, indexAxis) {
  6883. const options = this.options;
  6884. if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
  6885. const loop = options.spanGaps ? this._loop : this._fullLoop;
  6886. _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
  6887. this._pointsUpdated = true;
  6888. }
  6889. }
  6890. set points(points) {
  6891. this._points = points;
  6892. delete this._segments;
  6893. delete this._path;
  6894. this._pointsUpdated = false;
  6895. }
  6896. get points() {
  6897. return this._points;
  6898. }
  6899. get segments() {
  6900. return this._segments || (this._segments = _computeSegments(this, this.options.segment));
  6901. }
  6902. first() {
  6903. const segments = this.segments;
  6904. const points = this.points;
  6905. return segments.length && points[segments[0].start];
  6906. }
  6907. last() {
  6908. const segments = this.segments;
  6909. const points = this.points;
  6910. const count = segments.length;
  6911. return count && points[segments[count - 1].end];
  6912. }
  6913. interpolate(point, property) {
  6914. const options = this.options;
  6915. const value = point[property];
  6916. const points = this.points;
  6917. const segments = _boundSegments(this, {
  6918. property,
  6919. start: value,
  6920. end: value
  6921. });
  6922. if (!segments.length) {
  6923. return;
  6924. }
  6925. const result = [];
  6926. const _interpolate = _getInterpolationMethod(options);
  6927. let i, ilen;
  6928. for(i = 0, ilen = segments.length; i < ilen; ++i){
  6929. const { start , end } = segments[i];
  6930. const p1 = points[start];
  6931. const p2 = points[end];
  6932. if (p1 === p2) {
  6933. result.push(p1);
  6934. continue;
  6935. }
  6936. const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
  6937. const interpolated = _interpolate(p1, p2, t, options.stepped);
  6938. interpolated[property] = point[property];
  6939. result.push(interpolated);
  6940. }
  6941. return result.length === 1 ? result[0] : result;
  6942. }
  6943. pathSegment(ctx, segment, params) {
  6944. const segmentMethod = _getSegmentMethod(this);
  6945. return segmentMethod(ctx, this, segment, params);
  6946. }
  6947. path(ctx, start, count) {
  6948. const segments = this.segments;
  6949. const segmentMethod = _getSegmentMethod(this);
  6950. let loop = this._loop;
  6951. start = start || 0;
  6952. count = count || this.points.length - start;
  6953. for (const segment of segments){
  6954. loop &= segmentMethod(ctx, this, segment, {
  6955. start,
  6956. end: start + count - 1
  6957. });
  6958. }
  6959. return !!loop;
  6960. }
  6961. draw(ctx, chartArea, start, count) {
  6962. const options = this.options || {};
  6963. const points = this.points || [];
  6964. if (points.length && options.borderWidth) {
  6965. ctx.save();
  6966. draw(ctx, this, start, count);
  6967. ctx.restore();
  6968. }
  6969. if (this.animated) {
  6970. this._pointsUpdated = false;
  6971. this._path = undefined;
  6972. }
  6973. }
  6974. }
  6975. function inRange$1(el, pos, axis, useFinalPosition) {
  6976. const options = el.options;
  6977. const { [axis]: value } = el.getProps([
  6978. axis
  6979. ], useFinalPosition);
  6980. return Math.abs(pos - value) < options.radius + options.hitRadius;
  6981. }
  6982. class PointElement extends Element {
  6983. static id = 'point';
  6984. parsed;
  6985. skip;
  6986. stop;
  6987. /**
  6988. * @type {any}
  6989. */ static defaults = {
  6990. borderWidth: 1,
  6991. hitRadius: 1,
  6992. hoverBorderWidth: 1,
  6993. hoverRadius: 4,
  6994. pointStyle: 'circle',
  6995. radius: 3,
  6996. rotation: 0
  6997. };
  6998. /**
  6999. * @type {any}
  7000. */ static defaultRoutes = {
  7001. backgroundColor: 'backgroundColor',
  7002. borderColor: 'borderColor'
  7003. };
  7004. constructor(cfg){
  7005. super();
  7006. this.options = undefined;
  7007. this.parsed = undefined;
  7008. this.skip = undefined;
  7009. this.stop = undefined;
  7010. if (cfg) {
  7011. Object.assign(this, cfg);
  7012. }
  7013. }
  7014. inRange(mouseX, mouseY, useFinalPosition) {
  7015. const options = this.options;
  7016. const { x , y } = this.getProps([
  7017. 'x',
  7018. 'y'
  7019. ], useFinalPosition);
  7020. return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
  7021. }
  7022. inXRange(mouseX, useFinalPosition) {
  7023. return inRange$1(this, mouseX, 'x', useFinalPosition);
  7024. }
  7025. inYRange(mouseY, useFinalPosition) {
  7026. return inRange$1(this, mouseY, 'y', useFinalPosition);
  7027. }
  7028. getCenterPoint(useFinalPosition) {
  7029. const { x , y } = this.getProps([
  7030. 'x',
  7031. 'y'
  7032. ], useFinalPosition);
  7033. return {
  7034. x,
  7035. y
  7036. };
  7037. }
  7038. size(options) {
  7039. options = options || this.options || {};
  7040. let radius = options.radius || 0;
  7041. radius = Math.max(radius, radius && options.hoverRadius || 0);
  7042. const borderWidth = radius && options.borderWidth || 0;
  7043. return (radius + borderWidth) * 2;
  7044. }
  7045. draw(ctx, area) {
  7046. const options = this.options;
  7047. if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
  7048. return;
  7049. }
  7050. ctx.strokeStyle = options.borderColor;
  7051. ctx.lineWidth = options.borderWidth;
  7052. ctx.fillStyle = options.backgroundColor;
  7053. drawPoint(ctx, options, this.x, this.y);
  7054. }
  7055. getRange() {
  7056. const options = this.options || {};
  7057. // @ts-expect-error Fallbacks should never be hit in practice
  7058. return options.radius + options.hitRadius;
  7059. }
  7060. }
  7061. function getBarBounds(bar, useFinalPosition) {
  7062. const { x , y , base , width , height } = bar.getProps([
  7063. 'x',
  7064. 'y',
  7065. 'base',
  7066. 'width',
  7067. 'height'
  7068. ], useFinalPosition);
  7069. let left, right, top, bottom, half;
  7070. if (bar.horizontal) {
  7071. half = height / 2;
  7072. left = Math.min(x, base);
  7073. right = Math.max(x, base);
  7074. top = y - half;
  7075. bottom = y + half;
  7076. } else {
  7077. half = width / 2;
  7078. left = x - half;
  7079. right = x + half;
  7080. top = Math.min(y, base);
  7081. bottom = Math.max(y, base);
  7082. }
  7083. return {
  7084. left,
  7085. top,
  7086. right,
  7087. bottom
  7088. };
  7089. }
  7090. function skipOrLimit(skip, value, min, max) {
  7091. return skip ? 0 : _limitValue(value, min, max);
  7092. }
  7093. function parseBorderWidth(bar, maxW, maxH) {
  7094. const value = bar.options.borderWidth;
  7095. const skip = bar.borderSkipped;
  7096. const o = toTRBL(value);
  7097. return {
  7098. t: skipOrLimit(skip.top, o.top, 0, maxH),
  7099. r: skipOrLimit(skip.right, o.right, 0, maxW),
  7100. b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
  7101. l: skipOrLimit(skip.left, o.left, 0, maxW)
  7102. };
  7103. }
  7104. function parseBorderRadius(bar, maxW, maxH) {
  7105. const { enableBorderRadius } = bar.getProps([
  7106. 'enableBorderRadius'
  7107. ]);
  7108. const value = bar.options.borderRadius;
  7109. const o = toTRBLCorners(value);
  7110. const maxR = Math.min(maxW, maxH);
  7111. const skip = bar.borderSkipped;
  7112. const enableBorder = enableBorderRadius || isObject(value);
  7113. return {
  7114. topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
  7115. topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
  7116. bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
  7117. bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
  7118. };
  7119. }
  7120. function boundingRects(bar) {
  7121. const bounds = getBarBounds(bar);
  7122. const width = bounds.right - bounds.left;
  7123. const height = bounds.bottom - bounds.top;
  7124. const border = parseBorderWidth(bar, width / 2, height / 2);
  7125. const radius = parseBorderRadius(bar, width / 2, height / 2);
  7126. return {
  7127. outer: {
  7128. x: bounds.left,
  7129. y: bounds.top,
  7130. w: width,
  7131. h: height,
  7132. radius
  7133. },
  7134. inner: {
  7135. x: bounds.left + border.l,
  7136. y: bounds.top + border.t,
  7137. w: width - border.l - border.r,
  7138. h: height - border.t - border.b,
  7139. radius: {
  7140. topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
  7141. topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
  7142. bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
  7143. bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
  7144. }
  7145. }
  7146. };
  7147. }
  7148. function inRange(bar, x, y, useFinalPosition) {
  7149. const skipX = x === null;
  7150. const skipY = y === null;
  7151. const skipBoth = skipX && skipY;
  7152. const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
  7153. return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom));
  7154. }
  7155. function hasRadius(radius) {
  7156. return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  7157. }
  7158. function addNormalRectPath(ctx, rect) {
  7159. ctx.rect(rect.x, rect.y, rect.w, rect.h);
  7160. }
  7161. function inflateRect(rect, amount, refRect = {}) {
  7162. const x = rect.x !== refRect.x ? -amount : 0;
  7163. const y = rect.y !== refRect.y ? -amount : 0;
  7164. const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
  7165. const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
  7166. return {
  7167. x: rect.x + x,
  7168. y: rect.y + y,
  7169. w: rect.w + w,
  7170. h: rect.h + h,
  7171. radius: rect.radius
  7172. };
  7173. }
  7174. class BarElement extends Element {
  7175. static id = 'bar';
  7176. static defaults = {
  7177. borderSkipped: 'start',
  7178. borderWidth: 0,
  7179. borderRadius: 0,
  7180. inflateAmount: 'auto',
  7181. pointStyle: undefined
  7182. };
  7183. static defaultRoutes = {
  7184. backgroundColor: 'backgroundColor',
  7185. borderColor: 'borderColor'
  7186. };
  7187. constructor(cfg){
  7188. super();
  7189. this.options = undefined;
  7190. this.horizontal = undefined;
  7191. this.base = undefined;
  7192. this.width = undefined;
  7193. this.height = undefined;
  7194. this.inflateAmount = undefined;
  7195. if (cfg) {
  7196. Object.assign(this, cfg);
  7197. }
  7198. }
  7199. draw(ctx) {
  7200. const { inflateAmount , options: { borderColor , backgroundColor } } = this;
  7201. const { inner , outer } = boundingRects(this);
  7202. const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
  7203. ctx.save();
  7204. if (outer.w !== inner.w || outer.h !== inner.h) {
  7205. ctx.beginPath();
  7206. addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
  7207. ctx.clip();
  7208. addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
  7209. ctx.fillStyle = borderColor;
  7210. ctx.fill('evenodd');
  7211. }
  7212. ctx.beginPath();
  7213. addRectPath(ctx, inflateRect(inner, inflateAmount));
  7214. ctx.fillStyle = backgroundColor;
  7215. ctx.fill();
  7216. ctx.restore();
  7217. }
  7218. inRange(mouseX, mouseY, useFinalPosition) {
  7219. return inRange(this, mouseX, mouseY, useFinalPosition);
  7220. }
  7221. inXRange(mouseX, useFinalPosition) {
  7222. return inRange(this, mouseX, null, useFinalPosition);
  7223. }
  7224. inYRange(mouseY, useFinalPosition) {
  7225. return inRange(this, null, mouseY, useFinalPosition);
  7226. }
  7227. getCenterPoint(useFinalPosition) {
  7228. const { x , y , base , horizontal } = this.getProps([
  7229. 'x',
  7230. 'y',
  7231. 'base',
  7232. 'horizontal'
  7233. ], useFinalPosition);
  7234. return {
  7235. x: horizontal ? (x + base) / 2 : x,
  7236. y: horizontal ? y : (y + base) / 2
  7237. };
  7238. }
  7239. getRange(axis) {
  7240. return axis === 'x' ? this.width / 2 : this.height / 2;
  7241. }
  7242. }
  7243. var elements = /*#__PURE__*/Object.freeze({
  7244. __proto__: null,
  7245. ArcElement: ArcElement,
  7246. BarElement: BarElement,
  7247. LineElement: LineElement,
  7248. PointElement: PointElement
  7249. });
  7250. const BORDER_COLORS = [
  7251. 'rgb(54, 162, 235)',
  7252. 'rgb(255, 99, 132)',
  7253. 'rgb(255, 159, 64)',
  7254. 'rgb(255, 205, 86)',
  7255. 'rgb(75, 192, 192)',
  7256. 'rgb(153, 102, 255)',
  7257. 'rgb(201, 203, 207)' // grey
  7258. ];
  7259. // Border colors with 50% transparency
  7260. const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
  7261. function getBorderColor(i) {
  7262. return BORDER_COLORS[i % BORDER_COLORS.length];
  7263. }
  7264. function getBackgroundColor(i) {
  7265. return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
  7266. }
  7267. function colorizeDefaultDataset(dataset, i) {
  7268. dataset.borderColor = getBorderColor(i);
  7269. dataset.backgroundColor = getBackgroundColor(i);
  7270. return ++i;
  7271. }
  7272. function colorizeDoughnutDataset(dataset, i) {
  7273. dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
  7274. return i;
  7275. }
  7276. function colorizePolarAreaDataset(dataset, i) {
  7277. dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
  7278. return i;
  7279. }
  7280. function getColorizer(chart) {
  7281. let i = 0;
  7282. return (dataset, datasetIndex)=>{
  7283. const controller = chart.getDatasetMeta(datasetIndex).controller;
  7284. if (controller instanceof DoughnutController) {
  7285. i = colorizeDoughnutDataset(dataset, i);
  7286. } else if (controller instanceof PolarAreaController) {
  7287. i = colorizePolarAreaDataset(dataset, i);
  7288. } else if (controller) {
  7289. i = colorizeDefaultDataset(dataset, i);
  7290. }
  7291. };
  7292. }
  7293. function containsColorsDefinitions(descriptors) {
  7294. let k;
  7295. for(k in descriptors){
  7296. if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
  7297. return true;
  7298. }
  7299. }
  7300. return false;
  7301. }
  7302. function containsColorsDefinition(descriptor) {
  7303. return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
  7304. }
  7305. var plugin_colors = {
  7306. id: 'colors',
  7307. defaults: {
  7308. enabled: true,
  7309. forceOverride: false
  7310. },
  7311. beforeLayout (chart, _args, options) {
  7312. if (!options.enabled) {
  7313. return;
  7314. }
  7315. const { data: { datasets } , options: chartOptions } = chart.config;
  7316. const { elements } = chartOptions;
  7317. if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) {
  7318. return;
  7319. }
  7320. const colorizer = getColorizer(chart);
  7321. datasets.forEach(colorizer);
  7322. }
  7323. };
  7324. function lttbDecimation(data, start, count, availableWidth, options) {
  7325. const samples = options.samples || availableWidth;
  7326. if (samples >= count) {
  7327. return data.slice(start, start + count);
  7328. }
  7329. const decimated = [];
  7330. const bucketWidth = (count - 2) / (samples - 2);
  7331. let sampledIndex = 0;
  7332. const endIndex = start + count - 1;
  7333. let a = start;
  7334. let i, maxAreaPoint, maxArea, area, nextA;
  7335. decimated[sampledIndex++] = data[a];
  7336. for(i = 0; i < samples - 2; i++){
  7337. let avgX = 0;
  7338. let avgY = 0;
  7339. let j;
  7340. const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
  7341. const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
  7342. const avgRangeLength = avgRangeEnd - avgRangeStart;
  7343. for(j = avgRangeStart; j < avgRangeEnd; j++){
  7344. avgX += data[j].x;
  7345. avgY += data[j].y;
  7346. }
  7347. avgX /= avgRangeLength;
  7348. avgY /= avgRangeLength;
  7349. const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
  7350. const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
  7351. const { x: pointAx , y: pointAy } = data[a];
  7352. maxArea = area = -1;
  7353. for(j = rangeOffs; j < rangeTo; j++){
  7354. area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
  7355. if (area > maxArea) {
  7356. maxArea = area;
  7357. maxAreaPoint = data[j];
  7358. nextA = j;
  7359. }
  7360. }
  7361. decimated[sampledIndex++] = maxAreaPoint;
  7362. a = nextA;
  7363. }
  7364. decimated[sampledIndex++] = data[endIndex];
  7365. return decimated;
  7366. }
  7367. function minMaxDecimation(data, start, count, availableWidth) {
  7368. let avgX = 0;
  7369. let countX = 0;
  7370. let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
  7371. const decimated = [];
  7372. const endIndex = start + count - 1;
  7373. const xMin = data[start].x;
  7374. const xMax = data[endIndex].x;
  7375. const dx = xMax - xMin;
  7376. for(i = start; i < start + count; ++i){
  7377. point = data[i];
  7378. x = (point.x - xMin) / dx * availableWidth;
  7379. y = point.y;
  7380. const truncX = x | 0;
  7381. if (truncX === prevX) {
  7382. if (y < minY) {
  7383. minY = y;
  7384. minIndex = i;
  7385. } else if (y > maxY) {
  7386. maxY = y;
  7387. maxIndex = i;
  7388. }
  7389. avgX = (countX * avgX + point.x) / ++countX;
  7390. } else {
  7391. const lastIndex = i - 1;
  7392. if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
  7393. const intermediateIndex1 = Math.min(minIndex, maxIndex);
  7394. const intermediateIndex2 = Math.max(minIndex, maxIndex);
  7395. if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
  7396. decimated.push({
  7397. ...data[intermediateIndex1],
  7398. x: avgX
  7399. });
  7400. }
  7401. if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
  7402. decimated.push({
  7403. ...data[intermediateIndex2],
  7404. x: avgX
  7405. });
  7406. }
  7407. }
  7408. if (i > 0 && lastIndex !== startIndex) {
  7409. decimated.push(data[lastIndex]);
  7410. }
  7411. decimated.push(point);
  7412. prevX = truncX;
  7413. countX = 0;
  7414. minY = maxY = y;
  7415. minIndex = maxIndex = startIndex = i;
  7416. }
  7417. }
  7418. return decimated;
  7419. }
  7420. function cleanDecimatedDataset(dataset) {
  7421. if (dataset._decimated) {
  7422. const data = dataset._data;
  7423. delete dataset._decimated;
  7424. delete dataset._data;
  7425. Object.defineProperty(dataset, 'data', {
  7426. configurable: true,
  7427. enumerable: true,
  7428. writable: true,
  7429. value: data
  7430. });
  7431. }
  7432. }
  7433. function cleanDecimatedData(chart) {
  7434. chart.data.datasets.forEach((dataset)=>{
  7435. cleanDecimatedDataset(dataset);
  7436. });
  7437. }
  7438. function getStartAndCountOfVisiblePointsSimplified(meta, points) {
  7439. const pointCount = points.length;
  7440. let start = 0;
  7441. let count;
  7442. const { iScale } = meta;
  7443. const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
  7444. if (minDefined) {
  7445. start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
  7446. }
  7447. if (maxDefined) {
  7448. count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
  7449. } else {
  7450. count = pointCount - start;
  7451. }
  7452. return {
  7453. start,
  7454. count
  7455. };
  7456. }
  7457. var plugin_decimation = {
  7458. id: 'decimation',
  7459. defaults: {
  7460. algorithm: 'min-max',
  7461. enabled: false
  7462. },
  7463. beforeElementsUpdate: (chart, args, options)=>{
  7464. if (!options.enabled) {
  7465. cleanDecimatedData(chart);
  7466. return;
  7467. }
  7468. const availableWidth = chart.width;
  7469. chart.data.datasets.forEach((dataset, datasetIndex)=>{
  7470. const { _data , indexAxis } = dataset;
  7471. const meta = chart.getDatasetMeta(datasetIndex);
  7472. const data = _data || dataset.data;
  7473. if (resolve([
  7474. indexAxis,
  7475. chart.options.indexAxis
  7476. ]) === 'y') {
  7477. return;
  7478. }
  7479. if (!meta.controller.supportsDecimation) {
  7480. return;
  7481. }
  7482. const xAxis = chart.scales[meta.xAxisID];
  7483. if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
  7484. return;
  7485. }
  7486. if (chart.options.parsing) {
  7487. return;
  7488. }
  7489. let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
  7490. const threshold = options.threshold || 4 * availableWidth;
  7491. if (count <= threshold) {
  7492. cleanDecimatedDataset(dataset);
  7493. return;
  7494. }
  7495. if (isNullOrUndef(_data)) {
  7496. dataset._data = data;
  7497. delete dataset.data;
  7498. Object.defineProperty(dataset, 'data', {
  7499. configurable: true,
  7500. enumerable: true,
  7501. get: function() {
  7502. return this._decimated;
  7503. },
  7504. set: function(d) {
  7505. this._data = d;
  7506. }
  7507. });
  7508. }
  7509. let decimated;
  7510. switch(options.algorithm){
  7511. case 'lttb':
  7512. decimated = lttbDecimation(data, start, count, availableWidth, options);
  7513. break;
  7514. case 'min-max':
  7515. decimated = minMaxDecimation(data, start, count, availableWidth);
  7516. break;
  7517. default:
  7518. throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
  7519. }
  7520. dataset._decimated = decimated;
  7521. });
  7522. },
  7523. destroy (chart) {
  7524. cleanDecimatedData(chart);
  7525. }
  7526. };
  7527. function _segments(line, target, property) {
  7528. const segments = line.segments;
  7529. const points = line.points;
  7530. const tpoints = target.points;
  7531. const parts = [];
  7532. for (const segment of segments){
  7533. let { start , end } = segment;
  7534. end = _findSegmentEnd(start, end, points);
  7535. const bounds = _getBounds(property, points[start], points[end], segment.loop);
  7536. if (!target.segments) {
  7537. parts.push({
  7538. source: segment,
  7539. target: bounds,
  7540. start: points[start],
  7541. end: points[end]
  7542. });
  7543. continue;
  7544. }
  7545. const targetSegments = _boundSegments(target, bounds);
  7546. for (const tgt of targetSegments){
  7547. const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
  7548. const fillSources = _boundSegment(segment, points, subBounds);
  7549. for (const fillSource of fillSources){
  7550. parts.push({
  7551. source: fillSource,
  7552. target: tgt,
  7553. start: {
  7554. [property]: _getEdge(bounds, subBounds, 'start', Math.max)
  7555. },
  7556. end: {
  7557. [property]: _getEdge(bounds, subBounds, 'end', Math.min)
  7558. }
  7559. });
  7560. }
  7561. }
  7562. }
  7563. return parts;
  7564. }
  7565. function _getBounds(property, first, last, loop) {
  7566. if (loop) {
  7567. return;
  7568. }
  7569. let start = first[property];
  7570. let end = last[property];
  7571. if (property === 'angle') {
  7572. start = _normalizeAngle(start);
  7573. end = _normalizeAngle(end);
  7574. }
  7575. return {
  7576. property,
  7577. start,
  7578. end
  7579. };
  7580. }
  7581. function _pointsFromSegments(boundary, line) {
  7582. const { x =null , y =null } = boundary || {};
  7583. const linePoints = line.points;
  7584. const points = [];
  7585. line.segments.forEach(({ start , end })=>{
  7586. end = _findSegmentEnd(start, end, linePoints);
  7587. const first = linePoints[start];
  7588. const last = linePoints[end];
  7589. if (y !== null) {
  7590. points.push({
  7591. x: first.x,
  7592. y
  7593. });
  7594. points.push({
  7595. x: last.x,
  7596. y
  7597. });
  7598. } else if (x !== null) {
  7599. points.push({
  7600. x,
  7601. y: first.y
  7602. });
  7603. points.push({
  7604. x,
  7605. y: last.y
  7606. });
  7607. }
  7608. });
  7609. return points;
  7610. }
  7611. function _findSegmentEnd(start, end, points) {
  7612. for(; end > start; end--){
  7613. const point = points[end];
  7614. if (!isNaN(point.x) && !isNaN(point.y)) {
  7615. break;
  7616. }
  7617. }
  7618. return end;
  7619. }
  7620. function _getEdge(a, b, prop, fn) {
  7621. if (a && b) {
  7622. return fn(a[prop], b[prop]);
  7623. }
  7624. return a ? a[prop] : b ? b[prop] : 0;
  7625. }
  7626. function _createBoundaryLine(boundary, line) {
  7627. let points = [];
  7628. let _loop = false;
  7629. if (isArray(boundary)) {
  7630. _loop = true;
  7631. points = boundary;
  7632. } else {
  7633. points = _pointsFromSegments(boundary, line);
  7634. }
  7635. return points.length ? new LineElement({
  7636. points,
  7637. options: {
  7638. tension: 0
  7639. },
  7640. _loop,
  7641. _fullLoop: _loop
  7642. }) : null;
  7643. }
  7644. function _shouldApplyFill(source) {
  7645. return source && source.fill !== false;
  7646. }
  7647. function _resolveTarget(sources, index, propagate) {
  7648. const source = sources[index];
  7649. let fill = source.fill;
  7650. const visited = [
  7651. index
  7652. ];
  7653. let target;
  7654. if (!propagate) {
  7655. return fill;
  7656. }
  7657. while(fill !== false && visited.indexOf(fill) === -1){
  7658. if (!isNumberFinite(fill)) {
  7659. return fill;
  7660. }
  7661. target = sources[fill];
  7662. if (!target) {
  7663. return false;
  7664. }
  7665. if (target.visible) {
  7666. return fill;
  7667. }
  7668. visited.push(fill);
  7669. fill = target.fill;
  7670. }
  7671. return false;
  7672. }
  7673. function _decodeFill(line, index, count) {
  7674. const fill = parseFillOption(line);
  7675. if (isObject(fill)) {
  7676. return isNaN(fill.value) ? false : fill;
  7677. }
  7678. let target = parseFloat(fill);
  7679. if (isNumberFinite(target) && Math.floor(target) === target) {
  7680. return decodeTargetIndex(fill[0], index, target, count);
  7681. }
  7682. return [
  7683. 'origin',
  7684. 'start',
  7685. 'end',
  7686. 'stack',
  7687. 'shape'
  7688. ].indexOf(fill) >= 0 && fill;
  7689. }
  7690. function decodeTargetIndex(firstCh, index, target, count) {
  7691. if (firstCh === '-' || firstCh === '+') {
  7692. target = index + target;
  7693. }
  7694. if (target === index || target < 0 || target >= count) {
  7695. return false;
  7696. }
  7697. return target;
  7698. }
  7699. function _getTargetPixel(fill, scale) {
  7700. let pixel = null;
  7701. if (fill === 'start') {
  7702. pixel = scale.bottom;
  7703. } else if (fill === 'end') {
  7704. pixel = scale.top;
  7705. } else if (isObject(fill)) {
  7706. pixel = scale.getPixelForValue(fill.value);
  7707. } else if (scale.getBasePixel) {
  7708. pixel = scale.getBasePixel();
  7709. }
  7710. return pixel;
  7711. }
  7712. function _getTargetValue(fill, scale, startValue) {
  7713. let value;
  7714. if (fill === 'start') {
  7715. value = startValue;
  7716. } else if (fill === 'end') {
  7717. value = scale.options.reverse ? scale.min : scale.max;
  7718. } else if (isObject(fill)) {
  7719. value = fill.value;
  7720. } else {
  7721. value = scale.getBaseValue();
  7722. }
  7723. return value;
  7724. }
  7725. function parseFillOption(line) {
  7726. const options = line.options;
  7727. const fillOption = options.fill;
  7728. let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
  7729. if (fill === undefined) {
  7730. fill = !!options.backgroundColor;
  7731. }
  7732. if (fill === false || fill === null) {
  7733. return false;
  7734. }
  7735. if (fill === true) {
  7736. return 'origin';
  7737. }
  7738. return fill;
  7739. }
  7740. function _buildStackLine(source) {
  7741. const { scale , index , line } = source;
  7742. const points = [];
  7743. const segments = line.segments;
  7744. const sourcePoints = line.points;
  7745. const linesBelow = getLinesBelow(scale, index);
  7746. linesBelow.push(_createBoundaryLine({
  7747. x: null,
  7748. y: scale.bottom
  7749. }, line));
  7750. for(let i = 0; i < segments.length; i++){
  7751. const segment = segments[i];
  7752. for(let j = segment.start; j <= segment.end; j++){
  7753. addPointsBelow(points, sourcePoints[j], linesBelow);
  7754. }
  7755. }
  7756. return new LineElement({
  7757. points,
  7758. options: {}
  7759. });
  7760. }
  7761. function getLinesBelow(scale, index) {
  7762. const below = [];
  7763. const metas = scale.getMatchingVisibleMetas('line');
  7764. for(let i = 0; i < metas.length; i++){
  7765. const meta = metas[i];
  7766. if (meta.index === index) {
  7767. break;
  7768. }
  7769. if (!meta.hidden) {
  7770. below.unshift(meta.dataset);
  7771. }
  7772. }
  7773. return below;
  7774. }
  7775. function addPointsBelow(points, sourcePoint, linesBelow) {
  7776. const postponed = [];
  7777. for(let j = 0; j < linesBelow.length; j++){
  7778. const line = linesBelow[j];
  7779. const { first , last , point } = findPoint(line, sourcePoint, 'x');
  7780. if (!point || first && last) {
  7781. continue;
  7782. }
  7783. if (first) {
  7784. postponed.unshift(point);
  7785. } else {
  7786. points.push(point);
  7787. if (!last) {
  7788. break;
  7789. }
  7790. }
  7791. }
  7792. points.push(...postponed);
  7793. }
  7794. function findPoint(line, sourcePoint, property) {
  7795. const point = line.interpolate(sourcePoint, property);
  7796. if (!point) {
  7797. return {};
  7798. }
  7799. const pointValue = point[property];
  7800. const segments = line.segments;
  7801. const linePoints = line.points;
  7802. let first = false;
  7803. let last = false;
  7804. for(let i = 0; i < segments.length; i++){
  7805. const segment = segments[i];
  7806. const firstValue = linePoints[segment.start][property];
  7807. const lastValue = linePoints[segment.end][property];
  7808. if (_isBetween(pointValue, firstValue, lastValue)) {
  7809. first = pointValue === firstValue;
  7810. last = pointValue === lastValue;
  7811. break;
  7812. }
  7813. }
  7814. return {
  7815. first,
  7816. last,
  7817. point
  7818. };
  7819. }
  7820. class simpleArc {
  7821. constructor(opts){
  7822. this.x = opts.x;
  7823. this.y = opts.y;
  7824. this.radius = opts.radius;
  7825. }
  7826. pathSegment(ctx, bounds, opts) {
  7827. const { x , y , radius } = this;
  7828. bounds = bounds || {
  7829. start: 0,
  7830. end: TAU
  7831. };
  7832. ctx.arc(x, y, radius, bounds.end, bounds.start, true);
  7833. return !opts.bounds;
  7834. }
  7835. interpolate(point) {
  7836. const { x , y , radius } = this;
  7837. const angle = point.angle;
  7838. return {
  7839. x: x + Math.cos(angle) * radius,
  7840. y: y + Math.sin(angle) * radius,
  7841. angle
  7842. };
  7843. }
  7844. }
  7845. function _getTarget(source) {
  7846. const { chart , fill , line } = source;
  7847. if (isNumberFinite(fill)) {
  7848. return getLineByIndex(chart, fill);
  7849. }
  7850. if (fill === 'stack') {
  7851. return _buildStackLine(source);
  7852. }
  7853. if (fill === 'shape') {
  7854. return true;
  7855. }
  7856. const boundary = computeBoundary(source);
  7857. if (boundary instanceof simpleArc) {
  7858. return boundary;
  7859. }
  7860. return _createBoundaryLine(boundary, line);
  7861. }
  7862. function getLineByIndex(chart, index) {
  7863. const meta = chart.getDatasetMeta(index);
  7864. const visible = meta && chart.isDatasetVisible(index);
  7865. return visible ? meta.dataset : null;
  7866. }
  7867. function computeBoundary(source) {
  7868. const scale = source.scale || {};
  7869. if (scale.getPointPositionForValue) {
  7870. return computeCircularBoundary(source);
  7871. }
  7872. return computeLinearBoundary(source);
  7873. }
  7874. function computeLinearBoundary(source) {
  7875. const { scale ={} , fill } = source;
  7876. const pixel = _getTargetPixel(fill, scale);
  7877. if (isNumberFinite(pixel)) {
  7878. const horizontal = scale.isHorizontal();
  7879. return {
  7880. x: horizontal ? pixel : null,
  7881. y: horizontal ? null : pixel
  7882. };
  7883. }
  7884. return null;
  7885. }
  7886. function computeCircularBoundary(source) {
  7887. const { scale , fill } = source;
  7888. const options = scale.options;
  7889. const length = scale.getLabels().length;
  7890. const start = options.reverse ? scale.max : scale.min;
  7891. const value = _getTargetValue(fill, scale, start);
  7892. const target = [];
  7893. if (options.grid.circular) {
  7894. const center = scale.getPointPositionForValue(0, start);
  7895. return new simpleArc({
  7896. x: center.x,
  7897. y: center.y,
  7898. radius: scale.getDistanceFromCenterForValue(value)
  7899. });
  7900. }
  7901. for(let i = 0; i < length; ++i){
  7902. target.push(scale.getPointPositionForValue(i, value));
  7903. }
  7904. return target;
  7905. }
  7906. function _drawfill(ctx, source, area) {
  7907. const target = _getTarget(source);
  7908. const { line , scale , axis } = source;
  7909. const lineOpts = line.options;
  7910. const fillOption = lineOpts.fill;
  7911. const color = lineOpts.backgroundColor;
  7912. const { above =color , below =color } = fillOption || {};
  7913. if (target && line.points.length) {
  7914. clipArea(ctx, area);
  7915. doFill(ctx, {
  7916. line,
  7917. target,
  7918. above,
  7919. below,
  7920. area,
  7921. scale,
  7922. axis
  7923. });
  7924. unclipArea(ctx);
  7925. }
  7926. }
  7927. function doFill(ctx, cfg) {
  7928. const { line , target , above , below , area , scale } = cfg;
  7929. const property = line._loop ? 'angle' : cfg.axis;
  7930. ctx.save();
  7931. if (property === 'x' && below !== above) {
  7932. clipVertical(ctx, target, area.top);
  7933. fill(ctx, {
  7934. line,
  7935. target,
  7936. color: above,
  7937. scale,
  7938. property
  7939. });
  7940. ctx.restore();
  7941. ctx.save();
  7942. clipVertical(ctx, target, area.bottom);
  7943. }
  7944. fill(ctx, {
  7945. line,
  7946. target,
  7947. color: below,
  7948. scale,
  7949. property
  7950. });
  7951. ctx.restore();
  7952. }
  7953. function clipVertical(ctx, target, clipY) {
  7954. const { segments , points } = target;
  7955. let first = true;
  7956. let lineLoop = false;
  7957. ctx.beginPath();
  7958. for (const segment of segments){
  7959. const { start , end } = segment;
  7960. const firstPoint = points[start];
  7961. const lastPoint = points[_findSegmentEnd(start, end, points)];
  7962. if (first) {
  7963. ctx.moveTo(firstPoint.x, firstPoint.y);
  7964. first = false;
  7965. } else {
  7966. ctx.lineTo(firstPoint.x, clipY);
  7967. ctx.lineTo(firstPoint.x, firstPoint.y);
  7968. }
  7969. lineLoop = !!target.pathSegment(ctx, segment, {
  7970. move: lineLoop
  7971. });
  7972. if (lineLoop) {
  7973. ctx.closePath();
  7974. } else {
  7975. ctx.lineTo(lastPoint.x, clipY);
  7976. }
  7977. }
  7978. ctx.lineTo(target.first().x, clipY);
  7979. ctx.closePath();
  7980. ctx.clip();
  7981. }
  7982. function fill(ctx, cfg) {
  7983. const { line , target , property , color , scale } = cfg;
  7984. const segments = _segments(line, target, property);
  7985. for (const { source: src , target: tgt , start , end } of segments){
  7986. const { style: { backgroundColor =color } = {} } = src;
  7987. const notShape = target !== true;
  7988. ctx.save();
  7989. ctx.fillStyle = backgroundColor;
  7990. clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
  7991. ctx.beginPath();
  7992. const lineLoop = !!line.pathSegment(ctx, src);
  7993. let loop;
  7994. if (notShape) {
  7995. if (lineLoop) {
  7996. ctx.closePath();
  7997. } else {
  7998. interpolatedLineTo(ctx, target, end, property);
  7999. }
  8000. const targetLoop = !!target.pathSegment(ctx, tgt, {
  8001. move: lineLoop,
  8002. reverse: true
  8003. });
  8004. loop = lineLoop && targetLoop;
  8005. if (!loop) {
  8006. interpolatedLineTo(ctx, target, start, property);
  8007. }
  8008. }
  8009. ctx.closePath();
  8010. ctx.fill(loop ? 'evenodd' : 'nonzero');
  8011. ctx.restore();
  8012. }
  8013. }
  8014. function clipBounds(ctx, scale, bounds) {
  8015. const { top , bottom } = scale.chart.chartArea;
  8016. const { property , start , end } = bounds || {};
  8017. if (property === 'x') {
  8018. ctx.beginPath();
  8019. ctx.rect(start, top, end - start, bottom - top);
  8020. ctx.clip();
  8021. }
  8022. }
  8023. function interpolatedLineTo(ctx, target, point, property) {
  8024. const interpolatedPoint = target.interpolate(point, property);
  8025. if (interpolatedPoint) {
  8026. ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  8027. }
  8028. }
  8029. var index = {
  8030. id: 'filler',
  8031. afterDatasetsUpdate (chart, _args, options) {
  8032. const count = (chart.data.datasets || []).length;
  8033. const sources = [];
  8034. let meta, i, line, source;
  8035. for(i = 0; i < count; ++i){
  8036. meta = chart.getDatasetMeta(i);
  8037. line = meta.dataset;
  8038. source = null;
  8039. if (line && line.options && line instanceof LineElement) {
  8040. source = {
  8041. visible: chart.isDatasetVisible(i),
  8042. index: i,
  8043. fill: _decodeFill(line, i, count),
  8044. chart,
  8045. axis: meta.controller.options.indexAxis,
  8046. scale: meta.vScale,
  8047. line
  8048. };
  8049. }
  8050. meta.$filler = source;
  8051. sources.push(source);
  8052. }
  8053. for(i = 0; i < count; ++i){
  8054. source = sources[i];
  8055. if (!source || source.fill === false) {
  8056. continue;
  8057. }
  8058. source.fill = _resolveTarget(sources, i, options.propagate);
  8059. }
  8060. },
  8061. beforeDraw (chart, _args, options) {
  8062. const draw = options.drawTime === 'beforeDraw';
  8063. const metasets = chart.getSortedVisibleDatasetMetas();
  8064. const area = chart.chartArea;
  8065. for(let i = metasets.length - 1; i >= 0; --i){
  8066. const source = metasets[i].$filler;
  8067. if (!source) {
  8068. continue;
  8069. }
  8070. source.line.updateControlPoints(area, source.axis);
  8071. if (draw && source.fill) {
  8072. _drawfill(chart.ctx, source, area);
  8073. }
  8074. }
  8075. },
  8076. beforeDatasetsDraw (chart, _args, options) {
  8077. if (options.drawTime !== 'beforeDatasetsDraw') {
  8078. return;
  8079. }
  8080. const metasets = chart.getSortedVisibleDatasetMetas();
  8081. for(let i = metasets.length - 1; i >= 0; --i){
  8082. const source = metasets[i].$filler;
  8083. if (_shouldApplyFill(source)) {
  8084. _drawfill(chart.ctx, source, chart.chartArea);
  8085. }
  8086. }
  8087. },
  8088. beforeDatasetDraw (chart, args, options) {
  8089. const source = args.meta.$filler;
  8090. if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
  8091. return;
  8092. }
  8093. _drawfill(chart.ctx, source, chart.chartArea);
  8094. },
  8095. defaults: {
  8096. propagate: true,
  8097. drawTime: 'beforeDatasetDraw'
  8098. }
  8099. };
  8100. const getBoxSize = (labelOpts, fontSize)=>{
  8101. let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
  8102. if (labelOpts.usePointStyle) {
  8103. boxHeight = Math.min(boxHeight, fontSize);
  8104. boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
  8105. }
  8106. return {
  8107. boxWidth,
  8108. boxHeight,
  8109. itemHeight: Math.max(fontSize, boxHeight)
  8110. };
  8111. };
  8112. const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  8113. class Legend extends Element {
  8114. constructor(config){
  8115. super();
  8116. this._added = false;
  8117. this.legendHitBoxes = [];
  8118. this._hoveredItem = null;
  8119. this.doughnutMode = false;
  8120. this.chart = config.chart;
  8121. this.options = config.options;
  8122. this.ctx = config.ctx;
  8123. this.legendItems = undefined;
  8124. this.columnSizes = undefined;
  8125. this.lineWidths = undefined;
  8126. this.maxHeight = undefined;
  8127. this.maxWidth = undefined;
  8128. this.top = undefined;
  8129. this.bottom = undefined;
  8130. this.left = undefined;
  8131. this.right = undefined;
  8132. this.height = undefined;
  8133. this.width = undefined;
  8134. this._margins = undefined;
  8135. this.position = undefined;
  8136. this.weight = undefined;
  8137. this.fullSize = undefined;
  8138. }
  8139. update(maxWidth, maxHeight, margins) {
  8140. this.maxWidth = maxWidth;
  8141. this.maxHeight = maxHeight;
  8142. this._margins = margins;
  8143. this.setDimensions();
  8144. this.buildLabels();
  8145. this.fit();
  8146. }
  8147. setDimensions() {
  8148. if (this.isHorizontal()) {
  8149. this.width = this.maxWidth;
  8150. this.left = this._margins.left;
  8151. this.right = this.width;
  8152. } else {
  8153. this.height = this.maxHeight;
  8154. this.top = this._margins.top;
  8155. this.bottom = this.height;
  8156. }
  8157. }
  8158. buildLabels() {
  8159. const labelOpts = this.options.labels || {};
  8160. let legendItems = callback(labelOpts.generateLabels, [
  8161. this.chart
  8162. ], this) || [];
  8163. if (labelOpts.filter) {
  8164. legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
  8165. }
  8166. if (labelOpts.sort) {
  8167. legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
  8168. }
  8169. if (this.options.reverse) {
  8170. legendItems.reverse();
  8171. }
  8172. this.legendItems = legendItems;
  8173. }
  8174. fit() {
  8175. const { options , ctx } = this;
  8176. if (!options.display) {
  8177. this.width = this.height = 0;
  8178. return;
  8179. }
  8180. const labelOpts = options.labels;
  8181. const labelFont = toFont(labelOpts.font);
  8182. const fontSize = labelFont.size;
  8183. const titleHeight = this._computeTitleHeight();
  8184. const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
  8185. let width, height;
  8186. ctx.font = labelFont.string;
  8187. if (this.isHorizontal()) {
  8188. width = this.maxWidth;
  8189. height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  8190. } else {
  8191. height = this.maxHeight;
  8192. width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
  8193. }
  8194. this.width = Math.min(width, options.maxWidth || this.maxWidth);
  8195. this.height = Math.min(height, options.maxHeight || this.maxHeight);
  8196. }
  8197. _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
  8198. const { ctx , maxWidth , options: { labels: { padding } } } = this;
  8199. const hitboxes = this.legendHitBoxes = [];
  8200. const lineWidths = this.lineWidths = [
  8201. 0
  8202. ];
  8203. const lineHeight = itemHeight + padding;
  8204. let totalHeight = titleHeight;
  8205. ctx.textAlign = 'left';
  8206. ctx.textBaseline = 'middle';
  8207. let row = -1;
  8208. let top = -lineHeight;
  8209. this.legendItems.forEach((legendItem, i)=>{
  8210. const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
  8211. if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
  8212. totalHeight += lineHeight;
  8213. lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
  8214. top += lineHeight;
  8215. row++;
  8216. }
  8217. hitboxes[i] = {
  8218. left: 0,
  8219. top,
  8220. row,
  8221. width: itemWidth,
  8222. height: itemHeight
  8223. };
  8224. lineWidths[lineWidths.length - 1] += itemWidth + padding;
  8225. });
  8226. return totalHeight;
  8227. }
  8228. _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
  8229. const { ctx , maxHeight , options: { labels: { padding } } } = this;
  8230. const hitboxes = this.legendHitBoxes = [];
  8231. const columnSizes = this.columnSizes = [];
  8232. const heightLimit = maxHeight - titleHeight;
  8233. let totalWidth = padding;
  8234. let currentColWidth = 0;
  8235. let currentColHeight = 0;
  8236. let left = 0;
  8237. let col = 0;
  8238. this.legendItems.forEach((legendItem, i)=>{
  8239. const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
  8240. if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
  8241. totalWidth += currentColWidth + padding;
  8242. columnSizes.push({
  8243. width: currentColWidth,
  8244. height: currentColHeight
  8245. });
  8246. left += currentColWidth + padding;
  8247. col++;
  8248. currentColWidth = currentColHeight = 0;
  8249. }
  8250. hitboxes[i] = {
  8251. left,
  8252. top: currentColHeight,
  8253. col,
  8254. width: itemWidth,
  8255. height: itemHeight
  8256. };
  8257. currentColWidth = Math.max(currentColWidth, itemWidth);
  8258. currentColHeight += itemHeight + padding;
  8259. });
  8260. totalWidth += currentColWidth;
  8261. columnSizes.push({
  8262. width: currentColWidth,
  8263. height: currentColHeight
  8264. });
  8265. return totalWidth;
  8266. }
  8267. adjustHitBoxes() {
  8268. if (!this.options.display) {
  8269. return;
  8270. }
  8271. const titleHeight = this._computeTitleHeight();
  8272. const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
  8273. const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
  8274. if (this.isHorizontal()) {
  8275. let row = 0;
  8276. let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8277. for (const hitbox of hitboxes){
  8278. if (row !== hitbox.row) {
  8279. row = hitbox.row;
  8280. left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8281. }
  8282. hitbox.top += this.top + titleHeight + padding;
  8283. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
  8284. left += hitbox.width + padding;
  8285. }
  8286. } else {
  8287. let col = 0;
  8288. let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8289. for (const hitbox of hitboxes){
  8290. if (hitbox.col !== col) {
  8291. col = hitbox.col;
  8292. top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8293. }
  8294. hitbox.top = top;
  8295. hitbox.left += this.left + padding;
  8296. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
  8297. top += hitbox.height + padding;
  8298. }
  8299. }
  8300. }
  8301. isHorizontal() {
  8302. return this.options.position === 'top' || this.options.position === 'bottom';
  8303. }
  8304. draw() {
  8305. if (this.options.display) {
  8306. const ctx = this.ctx;
  8307. clipArea(ctx, this);
  8308. this._draw();
  8309. unclipArea(ctx);
  8310. }
  8311. }
  8312. _draw() {
  8313. const { options: opts , columnSizes , lineWidths , ctx } = this;
  8314. const { align , labels: labelOpts } = opts;
  8315. const defaultColor = defaults.color;
  8316. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  8317. const labelFont = toFont(labelOpts.font);
  8318. const { padding } = labelOpts;
  8319. const fontSize = labelFont.size;
  8320. const halfFontSize = fontSize / 2;
  8321. let cursor;
  8322. this.drawTitle();
  8323. ctx.textAlign = rtlHelper.textAlign('left');
  8324. ctx.textBaseline = 'middle';
  8325. ctx.lineWidth = 0.5;
  8326. ctx.font = labelFont.string;
  8327. const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
  8328. const drawLegendBox = function(x, y, legendItem) {
  8329. if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
  8330. return;
  8331. }
  8332. ctx.save();
  8333. const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
  8334. ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
  8335. ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
  8336. ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
  8337. ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
  8338. ctx.lineWidth = lineWidth;
  8339. ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
  8340. ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
  8341. if (labelOpts.usePointStyle) {
  8342. const drawOptions = {
  8343. radius: boxHeight * Math.SQRT2 / 2,
  8344. pointStyle: legendItem.pointStyle,
  8345. rotation: legendItem.rotation,
  8346. borderWidth: lineWidth
  8347. };
  8348. const centerX = rtlHelper.xPlus(x, boxWidth / 2);
  8349. const centerY = y + halfFontSize;
  8350. drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
  8351. } else {
  8352. const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
  8353. const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
  8354. const borderRadius = toTRBLCorners(legendItem.borderRadius);
  8355. ctx.beginPath();
  8356. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  8357. addRoundedRectPath(ctx, {
  8358. x: xBoxLeft,
  8359. y: yBoxTop,
  8360. w: boxWidth,
  8361. h: boxHeight,
  8362. radius: borderRadius
  8363. });
  8364. } else {
  8365. ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
  8366. }
  8367. ctx.fill();
  8368. if (lineWidth !== 0) {
  8369. ctx.stroke();
  8370. }
  8371. }
  8372. ctx.restore();
  8373. };
  8374. const fillText = function(x, y, legendItem) {
  8375. renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
  8376. strikethrough: legendItem.hidden,
  8377. textAlign: rtlHelper.textAlign(legendItem.textAlign)
  8378. });
  8379. };
  8380. const isHorizontal = this.isHorizontal();
  8381. const titleHeight = this._computeTitleHeight();
  8382. if (isHorizontal) {
  8383. cursor = {
  8384. x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
  8385. y: this.top + padding + titleHeight,
  8386. line: 0
  8387. };
  8388. } else {
  8389. cursor = {
  8390. x: this.left + padding,
  8391. y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
  8392. line: 0
  8393. };
  8394. }
  8395. overrideTextDirection(this.ctx, opts.textDirection);
  8396. const lineHeight = itemHeight + padding;
  8397. this.legendItems.forEach((legendItem, i)=>{
  8398. ctx.strokeStyle = legendItem.fontColor;
  8399. ctx.fillStyle = legendItem.fontColor;
  8400. const textWidth = ctx.measureText(legendItem.text).width;
  8401. const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
  8402. const width = boxWidth + halfFontSize + textWidth;
  8403. let x = cursor.x;
  8404. let y = cursor.y;
  8405. rtlHelper.setWidth(this.width);
  8406. if (isHorizontal) {
  8407. if (i > 0 && x + width + padding > this.right) {
  8408. y = cursor.y += lineHeight;
  8409. cursor.line++;
  8410. x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
  8411. }
  8412. } else if (i > 0 && y + lineHeight > this.bottom) {
  8413. x = cursor.x = x + columnSizes[cursor.line].width + padding;
  8414. cursor.line++;
  8415. y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
  8416. }
  8417. const realX = rtlHelper.x(x);
  8418. drawLegendBox(realX, y, legendItem);
  8419. x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
  8420. fillText(rtlHelper.x(x), y, legendItem);
  8421. if (isHorizontal) {
  8422. cursor.x += width + padding;
  8423. } else if (typeof legendItem.text !== 'string') {
  8424. const fontLineHeight = labelFont.lineHeight;
  8425. cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
  8426. } else {
  8427. cursor.y += lineHeight;
  8428. }
  8429. });
  8430. restoreTextDirection(this.ctx, opts.textDirection);
  8431. }
  8432. drawTitle() {
  8433. const opts = this.options;
  8434. const titleOpts = opts.title;
  8435. const titleFont = toFont(titleOpts.font);
  8436. const titlePadding = toPadding(titleOpts.padding);
  8437. if (!titleOpts.display) {
  8438. return;
  8439. }
  8440. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  8441. const ctx = this.ctx;
  8442. const position = titleOpts.position;
  8443. const halfFontSize = titleFont.size / 2;
  8444. const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
  8445. let y;
  8446. let left = this.left;
  8447. let maxWidth = this.width;
  8448. if (this.isHorizontal()) {
  8449. maxWidth = Math.max(...this.lineWidths);
  8450. y = this.top + topPaddingPlusHalfFontSize;
  8451. left = _alignStartEnd(opts.align, left, this.right - maxWidth);
  8452. } else {
  8453. const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
  8454. y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
  8455. }
  8456. const x = _alignStartEnd(position, left, left + maxWidth);
  8457. ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
  8458. ctx.textBaseline = 'middle';
  8459. ctx.strokeStyle = titleOpts.color;
  8460. ctx.fillStyle = titleOpts.color;
  8461. ctx.font = titleFont.string;
  8462. renderText(ctx, titleOpts.text, x, y, titleFont);
  8463. }
  8464. _computeTitleHeight() {
  8465. const titleOpts = this.options.title;
  8466. const titleFont = toFont(titleOpts.font);
  8467. const titlePadding = toPadding(titleOpts.padding);
  8468. return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
  8469. }
  8470. _getLegendItemAt(x, y) {
  8471. let i, hitBox, lh;
  8472. if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) {
  8473. lh = this.legendHitBoxes;
  8474. for(i = 0; i < lh.length; ++i){
  8475. hitBox = lh[i];
  8476. if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
  8477. return this.legendItems[i];
  8478. }
  8479. }
  8480. }
  8481. return null;
  8482. }
  8483. handleEvent(e) {
  8484. const opts = this.options;
  8485. if (!isListened(e.type, opts)) {
  8486. return;
  8487. }
  8488. const hoveredItem = this._getLegendItemAt(e.x, e.y);
  8489. if (e.type === 'mousemove' || e.type === 'mouseout') {
  8490. const previous = this._hoveredItem;
  8491. const sameItem = itemsEqual(previous, hoveredItem);
  8492. if (previous && !sameItem) {
  8493. callback(opts.onLeave, [
  8494. e,
  8495. previous,
  8496. this
  8497. ], this);
  8498. }
  8499. this._hoveredItem = hoveredItem;
  8500. if (hoveredItem && !sameItem) {
  8501. callback(opts.onHover, [
  8502. e,
  8503. hoveredItem,
  8504. this
  8505. ], this);
  8506. }
  8507. } else if (hoveredItem) {
  8508. callback(opts.onClick, [
  8509. e,
  8510. hoveredItem,
  8511. this
  8512. ], this);
  8513. }
  8514. }
  8515. }
  8516. function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
  8517. const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
  8518. const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
  8519. return {
  8520. itemWidth,
  8521. itemHeight
  8522. };
  8523. }
  8524. function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
  8525. let legendItemText = legendItem.text;
  8526. if (legendItemText && typeof legendItemText !== 'string') {
  8527. legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
  8528. }
  8529. return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
  8530. }
  8531. function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
  8532. let itemHeight = _itemHeight;
  8533. if (typeof legendItem.text !== 'string') {
  8534. itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
  8535. }
  8536. return itemHeight;
  8537. }
  8538. function calculateLegendItemHeight(legendItem, fontLineHeight) {
  8539. const labelHeight = legendItem.text ? legendItem.text.length : 0;
  8540. return fontLineHeight * labelHeight;
  8541. }
  8542. function isListened(type, opts) {
  8543. if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
  8544. return true;
  8545. }
  8546. if (opts.onClick && (type === 'click' || type === 'mouseup')) {
  8547. return true;
  8548. }
  8549. return false;
  8550. }
  8551. var plugin_legend = {
  8552. id: 'legend',
  8553. _element: Legend,
  8554. start (chart, _args, options) {
  8555. const legend = chart.legend = new Legend({
  8556. ctx: chart.ctx,
  8557. options,
  8558. chart
  8559. });
  8560. layouts.configure(chart, legend, options);
  8561. layouts.addBox(chart, legend);
  8562. },
  8563. stop (chart) {
  8564. layouts.removeBox(chart, chart.legend);
  8565. delete chart.legend;
  8566. },
  8567. beforeUpdate (chart, _args, options) {
  8568. const legend = chart.legend;
  8569. layouts.configure(chart, legend, options);
  8570. legend.options = options;
  8571. },
  8572. afterUpdate (chart) {
  8573. const legend = chart.legend;
  8574. legend.buildLabels();
  8575. legend.adjustHitBoxes();
  8576. },
  8577. afterEvent (chart, args) {
  8578. if (!args.replay) {
  8579. chart.legend.handleEvent(args.event);
  8580. }
  8581. },
  8582. defaults: {
  8583. display: true,
  8584. position: 'top',
  8585. align: 'center',
  8586. fullSize: true,
  8587. reverse: false,
  8588. weight: 1000,
  8589. onClick (e, legendItem, legend) {
  8590. const index = legendItem.datasetIndex;
  8591. const ci = legend.chart;
  8592. if (ci.isDatasetVisible(index)) {
  8593. ci.hide(index);
  8594. legendItem.hidden = true;
  8595. } else {
  8596. ci.show(index);
  8597. legendItem.hidden = false;
  8598. }
  8599. },
  8600. onHover: null,
  8601. onLeave: null,
  8602. labels: {
  8603. color: (ctx)=>ctx.chart.options.color,
  8604. boxWidth: 40,
  8605. padding: 10,
  8606. generateLabels (chart) {
  8607. const datasets = chart.data.datasets;
  8608. const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
  8609. return chart._getSortedDatasetMetas().map((meta)=>{
  8610. const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
  8611. const borderWidth = toPadding(style.borderWidth);
  8612. return {
  8613. text: datasets[meta.index].label,
  8614. fillStyle: style.backgroundColor,
  8615. fontColor: color,
  8616. hidden: !meta.visible,
  8617. lineCap: style.borderCapStyle,
  8618. lineDash: style.borderDash,
  8619. lineDashOffset: style.borderDashOffset,
  8620. lineJoin: style.borderJoinStyle,
  8621. lineWidth: (borderWidth.width + borderWidth.height) / 4,
  8622. strokeStyle: style.borderColor,
  8623. pointStyle: pointStyle || style.pointStyle,
  8624. rotation: style.rotation,
  8625. textAlign: textAlign || style.textAlign,
  8626. borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
  8627. datasetIndex: meta.index
  8628. };
  8629. }, this);
  8630. }
  8631. },
  8632. title: {
  8633. color: (ctx)=>ctx.chart.options.color,
  8634. display: false,
  8635. position: 'center',
  8636. text: ''
  8637. }
  8638. },
  8639. descriptors: {
  8640. _scriptable: (name)=>!name.startsWith('on'),
  8641. labels: {
  8642. _scriptable: (name)=>![
  8643. 'generateLabels',
  8644. 'filter',
  8645. 'sort'
  8646. ].includes(name)
  8647. }
  8648. }
  8649. };
  8650. class Title extends Element {
  8651. constructor(config){
  8652. super();
  8653. this.chart = config.chart;
  8654. this.options = config.options;
  8655. this.ctx = config.ctx;
  8656. this._padding = undefined;
  8657. this.top = undefined;
  8658. this.bottom = undefined;
  8659. this.left = undefined;
  8660. this.right = undefined;
  8661. this.width = undefined;
  8662. this.height = undefined;
  8663. this.position = undefined;
  8664. this.weight = undefined;
  8665. this.fullSize = undefined;
  8666. }
  8667. update(maxWidth, maxHeight) {
  8668. const opts = this.options;
  8669. this.left = 0;
  8670. this.top = 0;
  8671. if (!opts.display) {
  8672. this.width = this.height = this.right = this.bottom = 0;
  8673. return;
  8674. }
  8675. this.width = this.right = maxWidth;
  8676. this.height = this.bottom = maxHeight;
  8677. const lineCount = isArray(opts.text) ? opts.text.length : 1;
  8678. this._padding = toPadding(opts.padding);
  8679. const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
  8680. if (this.isHorizontal()) {
  8681. this.height = textSize;
  8682. } else {
  8683. this.width = textSize;
  8684. }
  8685. }
  8686. isHorizontal() {
  8687. const pos = this.options.position;
  8688. return pos === 'top' || pos === 'bottom';
  8689. }
  8690. _drawArgs(offset) {
  8691. const { top , left , bottom , right , options } = this;
  8692. const align = options.align;
  8693. let rotation = 0;
  8694. let maxWidth, titleX, titleY;
  8695. if (this.isHorizontal()) {
  8696. titleX = _alignStartEnd(align, left, right);
  8697. titleY = top + offset;
  8698. maxWidth = right - left;
  8699. } else {
  8700. if (options.position === 'left') {
  8701. titleX = left + offset;
  8702. titleY = _alignStartEnd(align, bottom, top);
  8703. rotation = PI * -0.5;
  8704. } else {
  8705. titleX = right - offset;
  8706. titleY = _alignStartEnd(align, top, bottom);
  8707. rotation = PI * 0.5;
  8708. }
  8709. maxWidth = bottom - top;
  8710. }
  8711. return {
  8712. titleX,
  8713. titleY,
  8714. maxWidth,
  8715. rotation
  8716. };
  8717. }
  8718. draw() {
  8719. const ctx = this.ctx;
  8720. const opts = this.options;
  8721. if (!opts.display) {
  8722. return;
  8723. }
  8724. const fontOpts = toFont(opts.font);
  8725. const lineHeight = fontOpts.lineHeight;
  8726. const offset = lineHeight / 2 + this._padding.top;
  8727. const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
  8728. renderText(ctx, opts.text, 0, 0, fontOpts, {
  8729. color: opts.color,
  8730. maxWidth,
  8731. rotation,
  8732. textAlign: _toLeftRightCenter(opts.align),
  8733. textBaseline: 'middle',
  8734. translation: [
  8735. titleX,
  8736. titleY
  8737. ]
  8738. });
  8739. }
  8740. }
  8741. function createTitle(chart, titleOpts) {
  8742. const title = new Title({
  8743. ctx: chart.ctx,
  8744. options: titleOpts,
  8745. chart
  8746. });
  8747. layouts.configure(chart, title, titleOpts);
  8748. layouts.addBox(chart, title);
  8749. chart.titleBlock = title;
  8750. }
  8751. var plugin_title = {
  8752. id: 'title',
  8753. _element: Title,
  8754. start (chart, _args, options) {
  8755. createTitle(chart, options);
  8756. },
  8757. stop (chart) {
  8758. const titleBlock = chart.titleBlock;
  8759. layouts.removeBox(chart, titleBlock);
  8760. delete chart.titleBlock;
  8761. },
  8762. beforeUpdate (chart, _args, options) {
  8763. const title = chart.titleBlock;
  8764. layouts.configure(chart, title, options);
  8765. title.options = options;
  8766. },
  8767. defaults: {
  8768. align: 'center',
  8769. display: false,
  8770. font: {
  8771. weight: 'bold'
  8772. },
  8773. fullSize: true,
  8774. padding: 10,
  8775. position: 'top',
  8776. text: '',
  8777. weight: 2000
  8778. },
  8779. defaultRoutes: {
  8780. color: 'color'
  8781. },
  8782. descriptors: {
  8783. _scriptable: true,
  8784. _indexable: false
  8785. }
  8786. };
  8787. const map = new WeakMap();
  8788. var plugin_subtitle = {
  8789. id: 'subtitle',
  8790. start (chart, _args, options) {
  8791. const title = new Title({
  8792. ctx: chart.ctx,
  8793. options,
  8794. chart
  8795. });
  8796. layouts.configure(chart, title, options);
  8797. layouts.addBox(chart, title);
  8798. map.set(chart, title);
  8799. },
  8800. stop (chart) {
  8801. layouts.removeBox(chart, map.get(chart));
  8802. map.delete(chart);
  8803. },
  8804. beforeUpdate (chart, _args, options) {
  8805. const title = map.get(chart);
  8806. layouts.configure(chart, title, options);
  8807. title.options = options;
  8808. },
  8809. defaults: {
  8810. align: 'center',
  8811. display: false,
  8812. font: {
  8813. weight: 'normal'
  8814. },
  8815. fullSize: true,
  8816. padding: 0,
  8817. position: 'top',
  8818. text: '',
  8819. weight: 1500
  8820. },
  8821. defaultRoutes: {
  8822. color: 'color'
  8823. },
  8824. descriptors: {
  8825. _scriptable: true,
  8826. _indexable: false
  8827. }
  8828. };
  8829. const positioners = {
  8830. average (items) {
  8831. if (!items.length) {
  8832. return false;
  8833. }
  8834. let i, len;
  8835. let x = 0;
  8836. let y = 0;
  8837. let count = 0;
  8838. for(i = 0, len = items.length; i < len; ++i){
  8839. const el = items[i].element;
  8840. if (el && el.hasValue()) {
  8841. const pos = el.tooltipPosition();
  8842. x += pos.x;
  8843. y += pos.y;
  8844. ++count;
  8845. }
  8846. }
  8847. return {
  8848. x: x / count,
  8849. y: y / count
  8850. };
  8851. },
  8852. nearest (items, eventPosition) {
  8853. if (!items.length) {
  8854. return false;
  8855. }
  8856. let x = eventPosition.x;
  8857. let y = eventPosition.y;
  8858. let minDistance = Number.POSITIVE_INFINITY;
  8859. let i, len, nearestElement;
  8860. for(i = 0, len = items.length; i < len; ++i){
  8861. const el = items[i].element;
  8862. if (el && el.hasValue()) {
  8863. const center = el.getCenterPoint();
  8864. const d = distanceBetweenPoints(eventPosition, center);
  8865. if (d < minDistance) {
  8866. minDistance = d;
  8867. nearestElement = el;
  8868. }
  8869. }
  8870. }
  8871. if (nearestElement) {
  8872. const tp = nearestElement.tooltipPosition();
  8873. x = tp.x;
  8874. y = tp.y;
  8875. }
  8876. return {
  8877. x,
  8878. y
  8879. };
  8880. }
  8881. };
  8882. function pushOrConcat(base, toPush) {
  8883. if (toPush) {
  8884. if (isArray(toPush)) {
  8885. Array.prototype.push.apply(base, toPush);
  8886. } else {
  8887. base.push(toPush);
  8888. }
  8889. }
  8890. return base;
  8891. }
  8892. function splitNewlines(str) {
  8893. if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  8894. return str.split('\n');
  8895. }
  8896. return str;
  8897. }
  8898. function createTooltipItem(chart, item) {
  8899. const { element , datasetIndex , index } = item;
  8900. const controller = chart.getDatasetMeta(datasetIndex).controller;
  8901. const { label , value } = controller.getLabelAndValue(index);
  8902. return {
  8903. chart,
  8904. label,
  8905. parsed: controller.getParsed(index),
  8906. raw: chart.data.datasets[datasetIndex].data[index],
  8907. formattedValue: value,
  8908. dataset: controller.getDataset(),
  8909. dataIndex: index,
  8910. datasetIndex,
  8911. element
  8912. };
  8913. }
  8914. function getTooltipSize(tooltip, options) {
  8915. const ctx = tooltip.chart.ctx;
  8916. const { body , footer , title } = tooltip;
  8917. const { boxWidth , boxHeight } = options;
  8918. const bodyFont = toFont(options.bodyFont);
  8919. const titleFont = toFont(options.titleFont);
  8920. const footerFont = toFont(options.footerFont);
  8921. const titleLineCount = title.length;
  8922. const footerLineCount = footer.length;
  8923. const bodyLineItemCount = body.length;
  8924. const padding = toPadding(options.padding);
  8925. let height = padding.height;
  8926. let width = 0;
  8927. let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
  8928. combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
  8929. if (titleLineCount) {
  8930. height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
  8931. }
  8932. if (combinedBodyLength) {
  8933. const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
  8934. height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
  8935. }
  8936. if (footerLineCount) {
  8937. height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
  8938. }
  8939. let widthPadding = 0;
  8940. const maxLineWidth = function(line) {
  8941. width = Math.max(width, ctx.measureText(line).width + widthPadding);
  8942. };
  8943. ctx.save();
  8944. ctx.font = titleFont.string;
  8945. each(tooltip.title, maxLineWidth);
  8946. ctx.font = bodyFont.string;
  8947. each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
  8948. widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
  8949. each(body, (bodyItem)=>{
  8950. each(bodyItem.before, maxLineWidth);
  8951. each(bodyItem.lines, maxLineWidth);
  8952. each(bodyItem.after, maxLineWidth);
  8953. });
  8954. widthPadding = 0;
  8955. ctx.font = footerFont.string;
  8956. each(tooltip.footer, maxLineWidth);
  8957. ctx.restore();
  8958. width += padding.width;
  8959. return {
  8960. width,
  8961. height
  8962. };
  8963. }
  8964. function determineYAlign(chart, size) {
  8965. const { y , height } = size;
  8966. if (y < height / 2) {
  8967. return 'top';
  8968. } else if (y > chart.height - height / 2) {
  8969. return 'bottom';
  8970. }
  8971. return 'center';
  8972. }
  8973. function doesNotFitWithAlign(xAlign, chart, options, size) {
  8974. const { x , width } = size;
  8975. const caret = options.caretSize + options.caretPadding;
  8976. if (xAlign === 'left' && x + width + caret > chart.width) {
  8977. return true;
  8978. }
  8979. if (xAlign === 'right' && x - width - caret < 0) {
  8980. return true;
  8981. }
  8982. }
  8983. function determineXAlign(chart, options, size, yAlign) {
  8984. const { x , width } = size;
  8985. const { width: chartWidth , chartArea: { left , right } } = chart;
  8986. let xAlign = 'center';
  8987. if (yAlign === 'center') {
  8988. xAlign = x <= (left + right) / 2 ? 'left' : 'right';
  8989. } else if (x <= width / 2) {
  8990. xAlign = 'left';
  8991. } else if (x >= chartWidth - width / 2) {
  8992. xAlign = 'right';
  8993. }
  8994. if (doesNotFitWithAlign(xAlign, chart, options, size)) {
  8995. xAlign = 'center';
  8996. }
  8997. return xAlign;
  8998. }
  8999. function determineAlignment(chart, options, size) {
  9000. const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
  9001. return {
  9002. xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
  9003. yAlign
  9004. };
  9005. }
  9006. function alignX(size, xAlign) {
  9007. let { x , width } = size;
  9008. if (xAlign === 'right') {
  9009. x -= width;
  9010. } else if (xAlign === 'center') {
  9011. x -= width / 2;
  9012. }
  9013. return x;
  9014. }
  9015. function alignY(size, yAlign, paddingAndSize) {
  9016. let { y , height } = size;
  9017. if (yAlign === 'top') {
  9018. y += paddingAndSize;
  9019. } else if (yAlign === 'bottom') {
  9020. y -= height + paddingAndSize;
  9021. } else {
  9022. y -= height / 2;
  9023. }
  9024. return y;
  9025. }
  9026. function getBackgroundPoint(options, size, alignment, chart) {
  9027. const { caretSize , caretPadding , cornerRadius } = options;
  9028. const { xAlign , yAlign } = alignment;
  9029. const paddingAndSize = caretSize + caretPadding;
  9030. const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
  9031. let x = alignX(size, xAlign);
  9032. const y = alignY(size, yAlign, paddingAndSize);
  9033. if (yAlign === 'center') {
  9034. if (xAlign === 'left') {
  9035. x += paddingAndSize;
  9036. } else if (xAlign === 'right') {
  9037. x -= paddingAndSize;
  9038. }
  9039. } else if (xAlign === 'left') {
  9040. x -= Math.max(topLeft, bottomLeft) + caretSize;
  9041. } else if (xAlign === 'right') {
  9042. x += Math.max(topRight, bottomRight) + caretSize;
  9043. }
  9044. return {
  9045. x: _limitValue(x, 0, chart.width - size.width),
  9046. y: _limitValue(y, 0, chart.height - size.height)
  9047. };
  9048. }
  9049. function getAlignedX(tooltip, align, options) {
  9050. const padding = toPadding(options.padding);
  9051. return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
  9052. }
  9053. function getBeforeAfterBodyLines(callback) {
  9054. return pushOrConcat([], splitNewlines(callback));
  9055. }
  9056. function createTooltipContext(parent, tooltip, tooltipItems) {
  9057. return createContext(parent, {
  9058. tooltip,
  9059. tooltipItems,
  9060. type: 'tooltip'
  9061. });
  9062. }
  9063. function overrideCallbacks(callbacks, context) {
  9064. const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  9065. return override ? callbacks.override(override) : callbacks;
  9066. }
  9067. const defaultCallbacks = {
  9068. beforeTitle: noop,
  9069. title (tooltipItems) {
  9070. if (tooltipItems.length > 0) {
  9071. const item = tooltipItems[0];
  9072. const labels = item.chart.data.labels;
  9073. const labelCount = labels ? labels.length : 0;
  9074. if (this && this.options && this.options.mode === 'dataset') {
  9075. return item.dataset.label || '';
  9076. } else if (item.label) {
  9077. return item.label;
  9078. } else if (labelCount > 0 && item.dataIndex < labelCount) {
  9079. return labels[item.dataIndex];
  9080. }
  9081. }
  9082. return '';
  9083. },
  9084. afterTitle: noop,
  9085. beforeBody: noop,
  9086. beforeLabel: noop,
  9087. label (tooltipItem) {
  9088. if (this && this.options && this.options.mode === 'dataset') {
  9089. return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
  9090. }
  9091. let label = tooltipItem.dataset.label || '';
  9092. if (label) {
  9093. label += ': ';
  9094. }
  9095. const value = tooltipItem.formattedValue;
  9096. if (!isNullOrUndef(value)) {
  9097. label += value;
  9098. }
  9099. return label;
  9100. },
  9101. labelColor (tooltipItem) {
  9102. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9103. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9104. return {
  9105. borderColor: options.borderColor,
  9106. backgroundColor: options.backgroundColor,
  9107. borderWidth: options.borderWidth,
  9108. borderDash: options.borderDash,
  9109. borderDashOffset: options.borderDashOffset,
  9110. borderRadius: 0
  9111. };
  9112. },
  9113. labelTextColor () {
  9114. return this.options.bodyColor;
  9115. },
  9116. labelPointStyle (tooltipItem) {
  9117. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9118. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9119. return {
  9120. pointStyle: options.pointStyle,
  9121. rotation: options.rotation
  9122. };
  9123. },
  9124. afterLabel: noop,
  9125. afterBody: noop,
  9126. beforeFooter: noop,
  9127. footer: noop,
  9128. afterFooter: noop
  9129. };
  9130. function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
  9131. const result = callbacks[name].call(ctx, arg);
  9132. if (typeof result === 'undefined') {
  9133. return defaultCallbacks[name].call(ctx, arg);
  9134. }
  9135. return result;
  9136. }
  9137. class Tooltip extends Element {
  9138. static positioners = positioners;
  9139. constructor(config){
  9140. super();
  9141. this.opacity = 0;
  9142. this._active = [];
  9143. this._eventPosition = undefined;
  9144. this._size = undefined;
  9145. this._cachedAnimations = undefined;
  9146. this._tooltipItems = [];
  9147. this.$animations = undefined;
  9148. this.$context = undefined;
  9149. this.chart = config.chart;
  9150. this.options = config.options;
  9151. this.dataPoints = undefined;
  9152. this.title = undefined;
  9153. this.beforeBody = undefined;
  9154. this.body = undefined;
  9155. this.afterBody = undefined;
  9156. this.footer = undefined;
  9157. this.xAlign = undefined;
  9158. this.yAlign = undefined;
  9159. this.x = undefined;
  9160. this.y = undefined;
  9161. this.height = undefined;
  9162. this.width = undefined;
  9163. this.caretX = undefined;
  9164. this.caretY = undefined;
  9165. this.labelColors = undefined;
  9166. this.labelPointStyles = undefined;
  9167. this.labelTextColors = undefined;
  9168. }
  9169. initialize(options) {
  9170. this.options = options;
  9171. this._cachedAnimations = undefined;
  9172. this.$context = undefined;
  9173. }
  9174. _resolveAnimations() {
  9175. const cached = this._cachedAnimations;
  9176. if (cached) {
  9177. return cached;
  9178. }
  9179. const chart = this.chart;
  9180. const options = this.options.setContext(this.getContext());
  9181. const opts = options.enabled && chart.options.animation && options.animations;
  9182. const animations = new Animations(this.chart, opts);
  9183. if (opts._cacheable) {
  9184. this._cachedAnimations = Object.freeze(animations);
  9185. }
  9186. return animations;
  9187. }
  9188. getContext() {
  9189. return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
  9190. }
  9191. getTitle(context, options) {
  9192. const { callbacks } = options;
  9193. const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
  9194. const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
  9195. const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
  9196. let lines = [];
  9197. lines = pushOrConcat(lines, splitNewlines(beforeTitle));
  9198. lines = pushOrConcat(lines, splitNewlines(title));
  9199. lines = pushOrConcat(lines, splitNewlines(afterTitle));
  9200. return lines;
  9201. }
  9202. getBeforeBody(tooltipItems, options) {
  9203. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
  9204. }
  9205. getBody(tooltipItems, options) {
  9206. const { callbacks } = options;
  9207. const bodyItems = [];
  9208. each(tooltipItems, (context)=>{
  9209. const bodyItem = {
  9210. before: [],
  9211. lines: [],
  9212. after: []
  9213. };
  9214. const scoped = overrideCallbacks(callbacks, context);
  9215. pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
  9216. pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
  9217. pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
  9218. bodyItems.push(bodyItem);
  9219. });
  9220. return bodyItems;
  9221. }
  9222. getAfterBody(tooltipItems, options) {
  9223. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
  9224. }
  9225. getFooter(tooltipItems, options) {
  9226. const { callbacks } = options;
  9227. const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
  9228. const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
  9229. const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
  9230. let lines = [];
  9231. lines = pushOrConcat(lines, splitNewlines(beforeFooter));
  9232. lines = pushOrConcat(lines, splitNewlines(footer));
  9233. lines = pushOrConcat(lines, splitNewlines(afterFooter));
  9234. return lines;
  9235. }
  9236. _createItems(options) {
  9237. const active = this._active;
  9238. const data = this.chart.data;
  9239. const labelColors = [];
  9240. const labelPointStyles = [];
  9241. const labelTextColors = [];
  9242. let tooltipItems = [];
  9243. let i, len;
  9244. for(i = 0, len = active.length; i < len; ++i){
  9245. tooltipItems.push(createTooltipItem(this.chart, active[i]));
  9246. }
  9247. if (options.filter) {
  9248. tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
  9249. }
  9250. if (options.itemSort) {
  9251. tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
  9252. }
  9253. each(tooltipItems, (context)=>{
  9254. const scoped = overrideCallbacks(options.callbacks, context);
  9255. labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
  9256. labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
  9257. labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
  9258. });
  9259. this.labelColors = labelColors;
  9260. this.labelPointStyles = labelPointStyles;
  9261. this.labelTextColors = labelTextColors;
  9262. this.dataPoints = tooltipItems;
  9263. return tooltipItems;
  9264. }
  9265. update(changed, replay) {
  9266. const options = this.options.setContext(this.getContext());
  9267. const active = this._active;
  9268. let properties;
  9269. let tooltipItems = [];
  9270. if (!active.length) {
  9271. if (this.opacity !== 0) {
  9272. properties = {
  9273. opacity: 0
  9274. };
  9275. }
  9276. } else {
  9277. const position = positioners[options.position].call(this, active, this._eventPosition);
  9278. tooltipItems = this._createItems(options);
  9279. this.title = this.getTitle(tooltipItems, options);
  9280. this.beforeBody = this.getBeforeBody(tooltipItems, options);
  9281. this.body = this.getBody(tooltipItems, options);
  9282. this.afterBody = this.getAfterBody(tooltipItems, options);
  9283. this.footer = this.getFooter(tooltipItems, options);
  9284. const size = this._size = getTooltipSize(this, options);
  9285. const positionAndSize = Object.assign({}, position, size);
  9286. const alignment = determineAlignment(this.chart, options, positionAndSize);
  9287. const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
  9288. this.xAlign = alignment.xAlign;
  9289. this.yAlign = alignment.yAlign;
  9290. properties = {
  9291. opacity: 1,
  9292. x: backgroundPoint.x,
  9293. y: backgroundPoint.y,
  9294. width: size.width,
  9295. height: size.height,
  9296. caretX: position.x,
  9297. caretY: position.y
  9298. };
  9299. }
  9300. this._tooltipItems = tooltipItems;
  9301. this.$context = undefined;
  9302. if (properties) {
  9303. this._resolveAnimations().update(this, properties);
  9304. }
  9305. if (changed && options.external) {
  9306. options.external.call(this, {
  9307. chart: this.chart,
  9308. tooltip: this,
  9309. replay
  9310. });
  9311. }
  9312. }
  9313. drawCaret(tooltipPoint, ctx, size, options) {
  9314. const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
  9315. ctx.lineTo(caretPosition.x1, caretPosition.y1);
  9316. ctx.lineTo(caretPosition.x2, caretPosition.y2);
  9317. ctx.lineTo(caretPosition.x3, caretPosition.y3);
  9318. }
  9319. getCaretPosition(tooltipPoint, size, options) {
  9320. const { xAlign , yAlign } = this;
  9321. const { caretSize , cornerRadius } = options;
  9322. const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
  9323. const { x: ptX , y: ptY } = tooltipPoint;
  9324. const { width , height } = size;
  9325. let x1, x2, x3, y1, y2, y3;
  9326. if (yAlign === 'center') {
  9327. y2 = ptY + height / 2;
  9328. if (xAlign === 'left') {
  9329. x1 = ptX;
  9330. x2 = x1 - caretSize;
  9331. y1 = y2 + caretSize;
  9332. y3 = y2 - caretSize;
  9333. } else {
  9334. x1 = ptX + width;
  9335. x2 = x1 + caretSize;
  9336. y1 = y2 - caretSize;
  9337. y3 = y2 + caretSize;
  9338. }
  9339. x3 = x1;
  9340. } else {
  9341. if (xAlign === 'left') {
  9342. x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
  9343. } else if (xAlign === 'right') {
  9344. x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
  9345. } else {
  9346. x2 = this.caretX;
  9347. }
  9348. if (yAlign === 'top') {
  9349. y1 = ptY;
  9350. y2 = y1 - caretSize;
  9351. x1 = x2 - caretSize;
  9352. x3 = x2 + caretSize;
  9353. } else {
  9354. y1 = ptY + height;
  9355. y2 = y1 + caretSize;
  9356. x1 = x2 + caretSize;
  9357. x3 = x2 - caretSize;
  9358. }
  9359. y3 = y1;
  9360. }
  9361. return {
  9362. x1,
  9363. x2,
  9364. x3,
  9365. y1,
  9366. y2,
  9367. y3
  9368. };
  9369. }
  9370. drawTitle(pt, ctx, options) {
  9371. const title = this.title;
  9372. const length = title.length;
  9373. let titleFont, titleSpacing, i;
  9374. if (length) {
  9375. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  9376. pt.x = getAlignedX(this, options.titleAlign, options);
  9377. ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
  9378. ctx.textBaseline = 'middle';
  9379. titleFont = toFont(options.titleFont);
  9380. titleSpacing = options.titleSpacing;
  9381. ctx.fillStyle = options.titleColor;
  9382. ctx.font = titleFont.string;
  9383. for(i = 0; i < length; ++i){
  9384. ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
  9385. pt.y += titleFont.lineHeight + titleSpacing;
  9386. if (i + 1 === length) {
  9387. pt.y += options.titleMarginBottom - titleSpacing;
  9388. }
  9389. }
  9390. }
  9391. }
  9392. _drawColorBox(ctx, pt, i, rtlHelper, options) {
  9393. const labelColor = this.labelColors[i];
  9394. const labelPointStyle = this.labelPointStyles[i];
  9395. const { boxHeight , boxWidth } = options;
  9396. const bodyFont = toFont(options.bodyFont);
  9397. const colorX = getAlignedX(this, 'left', options);
  9398. const rtlColorX = rtlHelper.x(colorX);
  9399. const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
  9400. const colorY = pt.y + yOffSet;
  9401. if (options.usePointStyle) {
  9402. const drawOptions = {
  9403. radius: Math.min(boxWidth, boxHeight) / 2,
  9404. pointStyle: labelPointStyle.pointStyle,
  9405. rotation: labelPointStyle.rotation,
  9406. borderWidth: 1
  9407. };
  9408. const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
  9409. const centerY = colorY + boxHeight / 2;
  9410. ctx.strokeStyle = options.multiKeyBackground;
  9411. ctx.fillStyle = options.multiKeyBackground;
  9412. drawPoint(ctx, drawOptions, centerX, centerY);
  9413. ctx.strokeStyle = labelColor.borderColor;
  9414. ctx.fillStyle = labelColor.backgroundColor;
  9415. drawPoint(ctx, drawOptions, centerX, centerY);
  9416. } else {
  9417. ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1;
  9418. ctx.strokeStyle = labelColor.borderColor;
  9419. ctx.setLineDash(labelColor.borderDash || []);
  9420. ctx.lineDashOffset = labelColor.borderDashOffset || 0;
  9421. const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
  9422. const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
  9423. const borderRadius = toTRBLCorners(labelColor.borderRadius);
  9424. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  9425. ctx.beginPath();
  9426. ctx.fillStyle = options.multiKeyBackground;
  9427. addRoundedRectPath(ctx, {
  9428. x: outerX,
  9429. y: colorY,
  9430. w: boxWidth,
  9431. h: boxHeight,
  9432. radius: borderRadius
  9433. });
  9434. ctx.fill();
  9435. ctx.stroke();
  9436. ctx.fillStyle = labelColor.backgroundColor;
  9437. ctx.beginPath();
  9438. addRoundedRectPath(ctx, {
  9439. x: innerX,
  9440. y: colorY + 1,
  9441. w: boxWidth - 2,
  9442. h: boxHeight - 2,
  9443. radius: borderRadius
  9444. });
  9445. ctx.fill();
  9446. } else {
  9447. ctx.fillStyle = options.multiKeyBackground;
  9448. ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
  9449. ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
  9450. ctx.fillStyle = labelColor.backgroundColor;
  9451. ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
  9452. }
  9453. }
  9454. ctx.fillStyle = this.labelTextColors[i];
  9455. }
  9456. drawBody(pt, ctx, options) {
  9457. const { body } = this;
  9458. const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
  9459. const bodyFont = toFont(options.bodyFont);
  9460. let bodyLineHeight = bodyFont.lineHeight;
  9461. let xLinePadding = 0;
  9462. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  9463. const fillLineOfText = function(line) {
  9464. ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
  9465. pt.y += bodyLineHeight + bodySpacing;
  9466. };
  9467. const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
  9468. let bodyItem, textColor, lines, i, j, ilen, jlen;
  9469. ctx.textAlign = bodyAlign;
  9470. ctx.textBaseline = 'middle';
  9471. ctx.font = bodyFont.string;
  9472. pt.x = getAlignedX(this, bodyAlignForCalculation, options);
  9473. ctx.fillStyle = options.bodyColor;
  9474. each(this.beforeBody, fillLineOfText);
  9475. xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
  9476. for(i = 0, ilen = body.length; i < ilen; ++i){
  9477. bodyItem = body[i];
  9478. textColor = this.labelTextColors[i];
  9479. ctx.fillStyle = textColor;
  9480. each(bodyItem.before, fillLineOfText);
  9481. lines = bodyItem.lines;
  9482. if (displayColors && lines.length) {
  9483. this._drawColorBox(ctx, pt, i, rtlHelper, options);
  9484. bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
  9485. }
  9486. for(j = 0, jlen = lines.length; j < jlen; ++j){
  9487. fillLineOfText(lines[j]);
  9488. bodyLineHeight = bodyFont.lineHeight;
  9489. }
  9490. each(bodyItem.after, fillLineOfText);
  9491. }
  9492. xLinePadding = 0;
  9493. bodyLineHeight = bodyFont.lineHeight;
  9494. each(this.afterBody, fillLineOfText);
  9495. pt.y -= bodySpacing;
  9496. }
  9497. drawFooter(pt, ctx, options) {
  9498. const footer = this.footer;
  9499. const length = footer.length;
  9500. let footerFont, i;
  9501. if (length) {
  9502. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  9503. pt.x = getAlignedX(this, options.footerAlign, options);
  9504. pt.y += options.footerMarginTop;
  9505. ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
  9506. ctx.textBaseline = 'middle';
  9507. footerFont = toFont(options.footerFont);
  9508. ctx.fillStyle = options.footerColor;
  9509. ctx.font = footerFont.string;
  9510. for(i = 0; i < length; ++i){
  9511. ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
  9512. pt.y += footerFont.lineHeight + options.footerSpacing;
  9513. }
  9514. }
  9515. }
  9516. drawBackground(pt, ctx, tooltipSize, options) {
  9517. const { xAlign , yAlign } = this;
  9518. const { x , y } = pt;
  9519. const { width , height } = tooltipSize;
  9520. const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(options.cornerRadius);
  9521. ctx.fillStyle = options.backgroundColor;
  9522. ctx.strokeStyle = options.borderColor;
  9523. ctx.lineWidth = options.borderWidth;
  9524. ctx.beginPath();
  9525. ctx.moveTo(x + topLeft, y);
  9526. if (yAlign === 'top') {
  9527. this.drawCaret(pt, ctx, tooltipSize, options);
  9528. }
  9529. ctx.lineTo(x + width - topRight, y);
  9530. ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
  9531. if (yAlign === 'center' && xAlign === 'right') {
  9532. this.drawCaret(pt, ctx, tooltipSize, options);
  9533. }
  9534. ctx.lineTo(x + width, y + height - bottomRight);
  9535. ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
  9536. if (yAlign === 'bottom') {
  9537. this.drawCaret(pt, ctx, tooltipSize, options);
  9538. }
  9539. ctx.lineTo(x + bottomLeft, y + height);
  9540. ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
  9541. if (yAlign === 'center' && xAlign === 'left') {
  9542. this.drawCaret(pt, ctx, tooltipSize, options);
  9543. }
  9544. ctx.lineTo(x, y + topLeft);
  9545. ctx.quadraticCurveTo(x, y, x + topLeft, y);
  9546. ctx.closePath();
  9547. ctx.fill();
  9548. if (options.borderWidth > 0) {
  9549. ctx.stroke();
  9550. }
  9551. }
  9552. _updateAnimationTarget(options) {
  9553. const chart = this.chart;
  9554. const anims = this.$animations;
  9555. const animX = anims && anims.x;
  9556. const animY = anims && anims.y;
  9557. if (animX || animY) {
  9558. const position = positioners[options.position].call(this, this._active, this._eventPosition);
  9559. if (!position) {
  9560. return;
  9561. }
  9562. const size = this._size = getTooltipSize(this, options);
  9563. const positionAndSize = Object.assign({}, position, this._size);
  9564. const alignment = determineAlignment(chart, options, positionAndSize);
  9565. const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
  9566. if (animX._to !== point.x || animY._to !== point.y) {
  9567. this.xAlign = alignment.xAlign;
  9568. this.yAlign = alignment.yAlign;
  9569. this.width = size.width;
  9570. this.height = size.height;
  9571. this.caretX = position.x;
  9572. this.caretY = position.y;
  9573. this._resolveAnimations().update(this, point);
  9574. }
  9575. }
  9576. }
  9577. _willRender() {
  9578. return !!this.opacity;
  9579. }
  9580. draw(ctx) {
  9581. const options = this.options.setContext(this.getContext());
  9582. let opacity = this.opacity;
  9583. if (!opacity) {
  9584. return;
  9585. }
  9586. this._updateAnimationTarget(options);
  9587. const tooltipSize = {
  9588. width: this.width,
  9589. height: this.height
  9590. };
  9591. const pt = {
  9592. x: this.x,
  9593. y: this.y
  9594. };
  9595. opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
  9596. const padding = toPadding(options.padding);
  9597. const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
  9598. if (options.enabled && hasTooltipContent) {
  9599. ctx.save();
  9600. ctx.globalAlpha = opacity;
  9601. this.drawBackground(pt, ctx, tooltipSize, options);
  9602. overrideTextDirection(ctx, options.textDirection);
  9603. pt.y += padding.top;
  9604. this.drawTitle(pt, ctx, options);
  9605. this.drawBody(pt, ctx, options);
  9606. this.drawFooter(pt, ctx, options);
  9607. restoreTextDirection(ctx, options.textDirection);
  9608. ctx.restore();
  9609. }
  9610. }
  9611. getActiveElements() {
  9612. return this._active || [];
  9613. }
  9614. setActiveElements(activeElements, eventPosition) {
  9615. const lastActive = this._active;
  9616. const active = activeElements.map(({ datasetIndex , index })=>{
  9617. const meta = this.chart.getDatasetMeta(datasetIndex);
  9618. if (!meta) {
  9619. throw new Error('Cannot find a dataset at index ' + datasetIndex);
  9620. }
  9621. return {
  9622. datasetIndex,
  9623. element: meta.data[index],
  9624. index
  9625. };
  9626. });
  9627. const changed = !_elementsEqual(lastActive, active);
  9628. const positionChanged = this._positionChanged(active, eventPosition);
  9629. if (changed || positionChanged) {
  9630. this._active = active;
  9631. this._eventPosition = eventPosition;
  9632. this._ignoreReplayEvents = true;
  9633. this.update(true);
  9634. }
  9635. }
  9636. handleEvent(e, replay, inChartArea = true) {
  9637. if (replay && this._ignoreReplayEvents) {
  9638. return false;
  9639. }
  9640. this._ignoreReplayEvents = false;
  9641. const options = this.options;
  9642. const lastActive = this._active || [];
  9643. const active = this._getActiveElements(e, lastActive, replay, inChartArea);
  9644. const positionChanged = this._positionChanged(active, e);
  9645. const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
  9646. if (changed) {
  9647. this._active = active;
  9648. if (options.enabled || options.external) {
  9649. this._eventPosition = {
  9650. x: e.x,
  9651. y: e.y
  9652. };
  9653. this.update(true, replay);
  9654. }
  9655. }
  9656. return changed;
  9657. }
  9658. _getActiveElements(e, lastActive, replay, inChartArea) {
  9659. const options = this.options;
  9660. if (e.type === 'mouseout') {
  9661. return [];
  9662. }
  9663. if (!inChartArea) {
  9664. return lastActive;
  9665. }
  9666. const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
  9667. if (options.reverse) {
  9668. active.reverse();
  9669. }
  9670. return active;
  9671. }
  9672. _positionChanged(active, e) {
  9673. const { caretX , caretY , options } = this;
  9674. const position = positioners[options.position].call(this, active, e);
  9675. return position !== false && (caretX !== position.x || caretY !== position.y);
  9676. }
  9677. }
  9678. var plugin_tooltip = {
  9679. id: 'tooltip',
  9680. _element: Tooltip,
  9681. positioners,
  9682. afterInit (chart, _args, options) {
  9683. if (options) {
  9684. chart.tooltip = new Tooltip({
  9685. chart,
  9686. options
  9687. });
  9688. }
  9689. },
  9690. beforeUpdate (chart, _args, options) {
  9691. if (chart.tooltip) {
  9692. chart.tooltip.initialize(options);
  9693. }
  9694. },
  9695. reset (chart, _args, options) {
  9696. if (chart.tooltip) {
  9697. chart.tooltip.initialize(options);
  9698. }
  9699. },
  9700. afterDraw (chart) {
  9701. const tooltip = chart.tooltip;
  9702. if (tooltip && tooltip._willRender()) {
  9703. const args = {
  9704. tooltip
  9705. };
  9706. if (chart.notifyPlugins('beforeTooltipDraw', {
  9707. ...args,
  9708. cancelable: true
  9709. }) === false) {
  9710. return;
  9711. }
  9712. tooltip.draw(chart.ctx);
  9713. chart.notifyPlugins('afterTooltipDraw', args);
  9714. }
  9715. },
  9716. afterEvent (chart, args) {
  9717. if (chart.tooltip) {
  9718. const useFinalPosition = args.replay;
  9719. if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
  9720. args.changed = true;
  9721. }
  9722. }
  9723. },
  9724. defaults: {
  9725. enabled: true,
  9726. external: null,
  9727. position: 'average',
  9728. backgroundColor: 'rgba(0,0,0,0.8)',
  9729. titleColor: '#fff',
  9730. titleFont: {
  9731. weight: 'bold'
  9732. },
  9733. titleSpacing: 2,
  9734. titleMarginBottom: 6,
  9735. titleAlign: 'left',
  9736. bodyColor: '#fff',
  9737. bodySpacing: 2,
  9738. bodyFont: {},
  9739. bodyAlign: 'left',
  9740. footerColor: '#fff',
  9741. footerSpacing: 2,
  9742. footerMarginTop: 6,
  9743. footerFont: {
  9744. weight: 'bold'
  9745. },
  9746. footerAlign: 'left',
  9747. padding: 6,
  9748. caretPadding: 2,
  9749. caretSize: 5,
  9750. cornerRadius: 6,
  9751. boxHeight: (ctx, opts)=>opts.bodyFont.size,
  9752. boxWidth: (ctx, opts)=>opts.bodyFont.size,
  9753. multiKeyBackground: '#fff',
  9754. displayColors: true,
  9755. boxPadding: 0,
  9756. borderColor: 'rgba(0,0,0,0)',
  9757. borderWidth: 0,
  9758. animation: {
  9759. duration: 400,
  9760. easing: 'easeOutQuart'
  9761. },
  9762. animations: {
  9763. numbers: {
  9764. type: 'number',
  9765. properties: [
  9766. 'x',
  9767. 'y',
  9768. 'width',
  9769. 'height',
  9770. 'caretX',
  9771. 'caretY'
  9772. ]
  9773. },
  9774. opacity: {
  9775. easing: 'linear',
  9776. duration: 200
  9777. }
  9778. },
  9779. callbacks: defaultCallbacks
  9780. },
  9781. defaultRoutes: {
  9782. bodyFont: 'font',
  9783. footerFont: 'font',
  9784. titleFont: 'font'
  9785. },
  9786. descriptors: {
  9787. _scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
  9788. _indexable: false,
  9789. callbacks: {
  9790. _scriptable: false,
  9791. _indexable: false
  9792. },
  9793. animation: {
  9794. _fallback: false
  9795. },
  9796. animations: {
  9797. _fallback: 'animation'
  9798. }
  9799. },
  9800. additionalOptionScopes: [
  9801. 'interaction'
  9802. ]
  9803. };
  9804. var plugins = /*#__PURE__*/Object.freeze({
  9805. __proto__: null,
  9806. Colors: plugin_colors,
  9807. Decimation: plugin_decimation,
  9808. Filler: index,
  9809. Legend: plugin_legend,
  9810. SubTitle: plugin_subtitle,
  9811. Title: plugin_title,
  9812. Tooltip: plugin_tooltip
  9813. });
  9814. const addIfString = (labels, raw, index, addedLabels)=>{
  9815. if (typeof raw === 'string') {
  9816. index = labels.push(raw) - 1;
  9817. addedLabels.unshift({
  9818. index,
  9819. label: raw
  9820. });
  9821. } else if (isNaN(raw)) {
  9822. index = null;
  9823. }
  9824. return index;
  9825. };
  9826. function findOrAddLabel(labels, raw, index, addedLabels) {
  9827. const first = labels.indexOf(raw);
  9828. if (first === -1) {
  9829. return addIfString(labels, raw, index, addedLabels);
  9830. }
  9831. const last = labels.lastIndexOf(raw);
  9832. return first !== last ? index : first;
  9833. }
  9834. const validIndex = (index, max)=>index === null ? null : _limitValue(Math.round(index), 0, max);
  9835. function _getLabelForValue(value) {
  9836. const labels = this.getLabels();
  9837. if (value >= 0 && value < labels.length) {
  9838. return labels[value];
  9839. }
  9840. return value;
  9841. }
  9842. class CategoryScale extends Scale {
  9843. static id = 'category';
  9844. static defaults = {
  9845. ticks: {
  9846. callback: _getLabelForValue
  9847. }
  9848. };
  9849. constructor(cfg){
  9850. super(cfg);
  9851. this._startValue = undefined;
  9852. this._valueRange = 0;
  9853. this._addedLabels = [];
  9854. }
  9855. init(scaleOptions) {
  9856. const added = this._addedLabels;
  9857. if (added.length) {
  9858. const labels = this.getLabels();
  9859. for (const { index , label } of added){
  9860. if (labels[index] === label) {
  9861. labels.splice(index, 1);
  9862. }
  9863. }
  9864. this._addedLabels = [];
  9865. }
  9866. super.init(scaleOptions);
  9867. }
  9868. parse(raw, index) {
  9869. if (isNullOrUndef(raw)) {
  9870. return null;
  9871. }
  9872. const labels = this.getLabels();
  9873. index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
  9874. return validIndex(index, labels.length - 1);
  9875. }
  9876. determineDataLimits() {
  9877. const { minDefined , maxDefined } = this.getUserBounds();
  9878. let { min , max } = this.getMinMax(true);
  9879. if (this.options.bounds === 'ticks') {
  9880. if (!minDefined) {
  9881. min = 0;
  9882. }
  9883. if (!maxDefined) {
  9884. max = this.getLabels().length - 1;
  9885. }
  9886. }
  9887. this.min = min;
  9888. this.max = max;
  9889. }
  9890. buildTicks() {
  9891. const min = this.min;
  9892. const max = this.max;
  9893. const offset = this.options.offset;
  9894. const ticks = [];
  9895. let labels = this.getLabels();
  9896. labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
  9897. this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
  9898. this._startValue = this.min - (offset ? 0.5 : 0);
  9899. for(let value = min; value <= max; value++){
  9900. ticks.push({
  9901. value
  9902. });
  9903. }
  9904. return ticks;
  9905. }
  9906. getLabelForValue(value) {
  9907. return _getLabelForValue.call(this, value);
  9908. }
  9909. configure() {
  9910. super.configure();
  9911. if (!this.isHorizontal()) {
  9912. this._reversePixels = !this._reversePixels;
  9913. }
  9914. }
  9915. getPixelForValue(value) {
  9916. if (typeof value !== 'number') {
  9917. value = this.parse(value);
  9918. }
  9919. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  9920. }
  9921. getPixelForTick(index) {
  9922. const ticks = this.ticks;
  9923. if (index < 0 || index > ticks.length - 1) {
  9924. return null;
  9925. }
  9926. return this.getPixelForValue(ticks[index].value);
  9927. }
  9928. getValueForPixel(pixel) {
  9929. return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
  9930. }
  9931. getBasePixel() {
  9932. return this.bottom;
  9933. }
  9934. }
  9935. function generateTicks$1(generationOptions, dataRange) {
  9936. const ticks = [];
  9937. const MIN_SPACING = 1e-14;
  9938. const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
  9939. const unit = step || 1;
  9940. const maxSpaces = maxTicks - 1;
  9941. const { min: rmin , max: rmax } = dataRange;
  9942. const minDefined = !isNullOrUndef(min);
  9943. const maxDefined = !isNullOrUndef(max);
  9944. const countDefined = !isNullOrUndef(count);
  9945. const minSpacing = (rmax - rmin) / (maxDigits + 1);
  9946. let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
  9947. let factor, niceMin, niceMax, numSpaces;
  9948. if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
  9949. return [
  9950. {
  9951. value: rmin
  9952. },
  9953. {
  9954. value: rmax
  9955. }
  9956. ];
  9957. }
  9958. numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
  9959. if (numSpaces > maxSpaces) {
  9960. spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
  9961. }
  9962. if (!isNullOrUndef(precision)) {
  9963. factor = Math.pow(10, precision);
  9964. spacing = Math.ceil(spacing * factor) / factor;
  9965. }
  9966. if (bounds === 'ticks') {
  9967. niceMin = Math.floor(rmin / spacing) * spacing;
  9968. niceMax = Math.ceil(rmax / spacing) * spacing;
  9969. } else {
  9970. niceMin = rmin;
  9971. niceMax = rmax;
  9972. }
  9973. if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
  9974. numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
  9975. spacing = (max - min) / numSpaces;
  9976. niceMin = min;
  9977. niceMax = max;
  9978. } else if (countDefined) {
  9979. niceMin = minDefined ? min : niceMin;
  9980. niceMax = maxDefined ? max : niceMax;
  9981. numSpaces = count - 1;
  9982. spacing = (niceMax - niceMin) / numSpaces;
  9983. } else {
  9984. numSpaces = (niceMax - niceMin) / spacing;
  9985. if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  9986. numSpaces = Math.round(numSpaces);
  9987. } else {
  9988. numSpaces = Math.ceil(numSpaces);
  9989. }
  9990. }
  9991. const decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin));
  9992. factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
  9993. niceMin = Math.round(niceMin * factor) / factor;
  9994. niceMax = Math.round(niceMax * factor) / factor;
  9995. let j = 0;
  9996. if (minDefined) {
  9997. if (includeBounds && niceMin !== min) {
  9998. ticks.push({
  9999. value: min
  10000. });
  10001. if (niceMin < min) {
  10002. j++;
  10003. }
  10004. if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
  10005. j++;
  10006. }
  10007. } else if (niceMin < min) {
  10008. j++;
  10009. }
  10010. }
  10011. for(; j < numSpaces; ++j){
  10012. const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
  10013. if (maxDefined && tickValue > max) {
  10014. break;
  10015. }
  10016. ticks.push({
  10017. value: tickValue
  10018. });
  10019. }
  10020. if (maxDefined && includeBounds && niceMax !== max) {
  10021. if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
  10022. ticks[ticks.length - 1].value = max;
  10023. } else {
  10024. ticks.push({
  10025. value: max
  10026. });
  10027. }
  10028. } else if (!maxDefined || niceMax === max) {
  10029. ticks.push({
  10030. value: niceMax
  10031. });
  10032. }
  10033. return ticks;
  10034. }
  10035. function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
  10036. const rad = toRadians(minRotation);
  10037. const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
  10038. const length = 0.75 * minSpacing * ('' + value).length;
  10039. return Math.min(minSpacing / ratio, length);
  10040. }
  10041. class LinearScaleBase extends Scale {
  10042. constructor(cfg){
  10043. super(cfg);
  10044. this.start = undefined;
  10045. this.end = undefined;
  10046. this._startValue = undefined;
  10047. this._endValue = undefined;
  10048. this._valueRange = 0;
  10049. }
  10050. parse(raw, index) {
  10051. if (isNullOrUndef(raw)) {
  10052. return null;
  10053. }
  10054. if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
  10055. return null;
  10056. }
  10057. return +raw;
  10058. }
  10059. handleTickRangeOptions() {
  10060. const { beginAtZero } = this.options;
  10061. const { minDefined , maxDefined } = this.getUserBounds();
  10062. let { min , max } = this;
  10063. const setMin = (v)=>min = minDefined ? min : v;
  10064. const setMax = (v)=>max = maxDefined ? max : v;
  10065. if (beginAtZero) {
  10066. const minSign = sign(min);
  10067. const maxSign = sign(max);
  10068. if (minSign < 0 && maxSign < 0) {
  10069. setMax(0);
  10070. } else if (minSign > 0 && maxSign > 0) {
  10071. setMin(0);
  10072. }
  10073. }
  10074. if (min === max) {
  10075. let offset = max === 0 ? 1 : Math.abs(max * 0.05);
  10076. setMax(max + offset);
  10077. if (!beginAtZero) {
  10078. setMin(min - offset);
  10079. }
  10080. }
  10081. this.min = min;
  10082. this.max = max;
  10083. }
  10084. getTickLimit() {
  10085. const tickOpts = this.options.ticks;
  10086. let { maxTicksLimit , stepSize } = tickOpts;
  10087. let maxTicks;
  10088. if (stepSize) {
  10089. maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
  10090. if (maxTicks > 1000) {
  10091. console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
  10092. maxTicks = 1000;
  10093. }
  10094. } else {
  10095. maxTicks = this.computeTickLimit();
  10096. maxTicksLimit = maxTicksLimit || 11;
  10097. }
  10098. if (maxTicksLimit) {
  10099. maxTicks = Math.min(maxTicksLimit, maxTicks);
  10100. }
  10101. return maxTicks;
  10102. }
  10103. computeTickLimit() {
  10104. return Number.POSITIVE_INFINITY;
  10105. }
  10106. buildTicks() {
  10107. const opts = this.options;
  10108. const tickOpts = opts.ticks;
  10109. let maxTicks = this.getTickLimit();
  10110. maxTicks = Math.max(2, maxTicks);
  10111. const numericGeneratorOptions = {
  10112. maxTicks,
  10113. bounds: opts.bounds,
  10114. min: opts.min,
  10115. max: opts.max,
  10116. precision: tickOpts.precision,
  10117. step: tickOpts.stepSize,
  10118. count: tickOpts.count,
  10119. maxDigits: this._maxDigits(),
  10120. horizontal: this.isHorizontal(),
  10121. minRotation: tickOpts.minRotation || 0,
  10122. includeBounds: tickOpts.includeBounds !== false
  10123. };
  10124. const dataRange = this._range || this;
  10125. const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
  10126. if (opts.bounds === 'ticks') {
  10127. _setMinAndMaxByKey(ticks, this, 'value');
  10128. }
  10129. if (opts.reverse) {
  10130. ticks.reverse();
  10131. this.start = this.max;
  10132. this.end = this.min;
  10133. } else {
  10134. this.start = this.min;
  10135. this.end = this.max;
  10136. }
  10137. return ticks;
  10138. }
  10139. configure() {
  10140. const ticks = this.ticks;
  10141. let start = this.min;
  10142. let end = this.max;
  10143. super.configure();
  10144. if (this.options.offset && ticks.length) {
  10145. const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
  10146. start -= offset;
  10147. end += offset;
  10148. }
  10149. this._startValue = start;
  10150. this._endValue = end;
  10151. this._valueRange = end - start;
  10152. }
  10153. getLabelForValue(value) {
  10154. return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10155. }
  10156. }
  10157. class LinearScale extends LinearScaleBase {
  10158. static id = 'linear';
  10159. static defaults = {
  10160. ticks: {
  10161. callback: Ticks.formatters.numeric
  10162. }
  10163. };
  10164. determineDataLimits() {
  10165. const { min , max } = this.getMinMax(true);
  10166. this.min = isNumberFinite(min) ? min : 0;
  10167. this.max = isNumberFinite(max) ? max : 1;
  10168. this.handleTickRangeOptions();
  10169. }
  10170. computeTickLimit() {
  10171. const horizontal = this.isHorizontal();
  10172. const length = horizontal ? this.width : this.height;
  10173. const minRotation = toRadians(this.options.ticks.minRotation);
  10174. const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
  10175. const tickFont = this._resolveTickFontOptions(0);
  10176. return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
  10177. }
  10178. getPixelForValue(value) {
  10179. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  10180. }
  10181. getValueForPixel(pixel) {
  10182. return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
  10183. }
  10184. }
  10185. const log10Floor = (v)=>Math.floor(log10(v));
  10186. const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
  10187. function isMajor(tickVal) {
  10188. const remain = tickVal / Math.pow(10, log10Floor(tickVal));
  10189. return remain === 1;
  10190. }
  10191. function steps(min, max, rangeExp) {
  10192. const rangeStep = Math.pow(10, rangeExp);
  10193. const start = Math.floor(min / rangeStep);
  10194. const end = Math.ceil(max / rangeStep);
  10195. return end - start;
  10196. }
  10197. function startExp(min, max) {
  10198. const range = max - min;
  10199. let rangeExp = log10Floor(range);
  10200. while(steps(min, max, rangeExp) > 10){
  10201. rangeExp++;
  10202. }
  10203. while(steps(min, max, rangeExp) < 10){
  10204. rangeExp--;
  10205. }
  10206. return Math.min(rangeExp, log10Floor(min));
  10207. }
  10208. function generateTicks(generationOptions, { min , max }) {
  10209. min = finiteOrDefault(generationOptions.min, min);
  10210. const ticks = [];
  10211. const minExp = log10Floor(min);
  10212. let exp = startExp(min, max);
  10213. let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
  10214. const stepSize = Math.pow(10, exp);
  10215. const base = minExp > exp ? Math.pow(10, minExp) : 0;
  10216. const start = Math.round((min - base) * precision) / precision;
  10217. const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
  10218. let significand = Math.floor((start - offset) / Math.pow(10, exp));
  10219. let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
  10220. while(value < max){
  10221. ticks.push({
  10222. value,
  10223. major: isMajor(value),
  10224. significand
  10225. });
  10226. if (significand >= 10) {
  10227. significand = significand < 15 ? 15 : 20;
  10228. } else {
  10229. significand++;
  10230. }
  10231. if (significand >= 20) {
  10232. exp++;
  10233. significand = 2;
  10234. precision = exp >= 0 ? 1 : precision;
  10235. }
  10236. value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
  10237. }
  10238. const lastTick = finiteOrDefault(generationOptions.max, value);
  10239. ticks.push({
  10240. value: lastTick,
  10241. major: isMajor(lastTick),
  10242. significand
  10243. });
  10244. return ticks;
  10245. }
  10246. class LogarithmicScale extends Scale {
  10247. static id = 'logarithmic';
  10248. static defaults = {
  10249. ticks: {
  10250. callback: Ticks.formatters.logarithmic,
  10251. major: {
  10252. enabled: true
  10253. }
  10254. }
  10255. };
  10256. constructor(cfg){
  10257. super(cfg);
  10258. this.start = undefined;
  10259. this.end = undefined;
  10260. this._startValue = undefined;
  10261. this._valueRange = 0;
  10262. }
  10263. parse(raw, index) {
  10264. const value = LinearScaleBase.prototype.parse.apply(this, [
  10265. raw,
  10266. index
  10267. ]);
  10268. if (value === 0) {
  10269. this._zero = true;
  10270. return undefined;
  10271. }
  10272. return isNumberFinite(value) && value > 0 ? value : null;
  10273. }
  10274. determineDataLimits() {
  10275. const { min , max } = this.getMinMax(true);
  10276. this.min = isNumberFinite(min) ? Math.max(0, min) : null;
  10277. this.max = isNumberFinite(max) ? Math.max(0, max) : null;
  10278. if (this.options.beginAtZero) {
  10279. this._zero = true;
  10280. }
  10281. if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) {
  10282. this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
  10283. }
  10284. this.handleTickRangeOptions();
  10285. }
  10286. handleTickRangeOptions() {
  10287. const { minDefined , maxDefined } = this.getUserBounds();
  10288. let min = this.min;
  10289. let max = this.max;
  10290. const setMin = (v)=>min = minDefined ? min : v;
  10291. const setMax = (v)=>max = maxDefined ? max : v;
  10292. if (min === max) {
  10293. if (min <= 0) {
  10294. setMin(1);
  10295. setMax(10);
  10296. } else {
  10297. setMin(changeExponent(min, -1));
  10298. setMax(changeExponent(max, +1));
  10299. }
  10300. }
  10301. if (min <= 0) {
  10302. setMin(changeExponent(max, -1));
  10303. }
  10304. if (max <= 0) {
  10305. setMax(changeExponent(min, +1));
  10306. }
  10307. this.min = min;
  10308. this.max = max;
  10309. }
  10310. buildTicks() {
  10311. const opts = this.options;
  10312. const generationOptions = {
  10313. min: this._userMin,
  10314. max: this._userMax
  10315. };
  10316. const ticks = generateTicks(generationOptions, this);
  10317. if (opts.bounds === 'ticks') {
  10318. _setMinAndMaxByKey(ticks, this, 'value');
  10319. }
  10320. if (opts.reverse) {
  10321. ticks.reverse();
  10322. this.start = this.max;
  10323. this.end = this.min;
  10324. } else {
  10325. this.start = this.min;
  10326. this.end = this.max;
  10327. }
  10328. return ticks;
  10329. }
  10330. getLabelForValue(value) {
  10331. return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10332. }
  10333. configure() {
  10334. const start = this.min;
  10335. super.configure();
  10336. this._startValue = log10(start);
  10337. this._valueRange = log10(this.max) - log10(start);
  10338. }
  10339. getPixelForValue(value) {
  10340. if (value === undefined || value === 0) {
  10341. value = this.min;
  10342. }
  10343. if (value === null || isNaN(value)) {
  10344. return NaN;
  10345. }
  10346. return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange);
  10347. }
  10348. getValueForPixel(pixel) {
  10349. const decimal = this.getDecimalForPixel(pixel);
  10350. return Math.pow(10, this._startValue + decimal * this._valueRange);
  10351. }
  10352. }
  10353. function getTickBackdropHeight(opts) {
  10354. const tickOpts = opts.ticks;
  10355. if (tickOpts.display && opts.display) {
  10356. const padding = toPadding(tickOpts.backdropPadding);
  10357. return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
  10358. }
  10359. return 0;
  10360. }
  10361. function measureLabelSize(ctx, font, label) {
  10362. label = isArray(label) ? label : [
  10363. label
  10364. ];
  10365. return {
  10366. w: _longestText(ctx, font.string, label),
  10367. h: label.length * font.lineHeight
  10368. };
  10369. }
  10370. function determineLimits(angle, pos, size, min, max) {
  10371. if (angle === min || angle === max) {
  10372. return {
  10373. start: pos - size / 2,
  10374. end: pos + size / 2
  10375. };
  10376. } else if (angle < min || angle > max) {
  10377. return {
  10378. start: pos - size,
  10379. end: pos
  10380. };
  10381. }
  10382. return {
  10383. start: pos,
  10384. end: pos + size
  10385. };
  10386. }
  10387. function fitWithPointLabels(scale) {
  10388. const orig = {
  10389. l: scale.left + scale._padding.left,
  10390. r: scale.right - scale._padding.right,
  10391. t: scale.top + scale._padding.top,
  10392. b: scale.bottom - scale._padding.bottom
  10393. };
  10394. const limits = Object.assign({}, orig);
  10395. const labelSizes = [];
  10396. const padding = [];
  10397. const valueCount = scale._pointLabels.length;
  10398. const pointLabelOpts = scale.options.pointLabels;
  10399. const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
  10400. for(let i = 0; i < valueCount; i++){
  10401. const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
  10402. padding[i] = opts.padding;
  10403. const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
  10404. const plFont = toFont(opts.font);
  10405. const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
  10406. labelSizes[i] = textSize;
  10407. const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
  10408. const angle = Math.round(toDegrees(angleRadians));
  10409. const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  10410. const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  10411. updateLimits(limits, orig, angleRadians, hLimits, vLimits);
  10412. }
  10413. scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
  10414. scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  10415. }
  10416. function updateLimits(limits, orig, angle, hLimits, vLimits) {
  10417. const sin = Math.abs(Math.sin(angle));
  10418. const cos = Math.abs(Math.cos(angle));
  10419. let x = 0;
  10420. let y = 0;
  10421. if (hLimits.start < orig.l) {
  10422. x = (orig.l - hLimits.start) / sin;
  10423. limits.l = Math.min(limits.l, orig.l - x);
  10424. } else if (hLimits.end > orig.r) {
  10425. x = (hLimits.end - orig.r) / sin;
  10426. limits.r = Math.max(limits.r, orig.r + x);
  10427. }
  10428. if (vLimits.start < orig.t) {
  10429. y = (orig.t - vLimits.start) / cos;
  10430. limits.t = Math.min(limits.t, orig.t - y);
  10431. } else if (vLimits.end > orig.b) {
  10432. y = (vLimits.end - orig.b) / cos;
  10433. limits.b = Math.max(limits.b, orig.b + y);
  10434. }
  10435. }
  10436. function createPointLabelItem(scale, index, itemOpts) {
  10437. const outerDistance = scale.drawingArea;
  10438. const { extra , additionalAngle , padding , size } = itemOpts;
  10439. const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
  10440. const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
  10441. const y = yForAngle(pointLabelPosition.y, size.h, angle);
  10442. const textAlign = getTextAlignForAngle(angle);
  10443. const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
  10444. return {
  10445. visible: true,
  10446. x: pointLabelPosition.x,
  10447. y,
  10448. textAlign,
  10449. left,
  10450. top: y,
  10451. right: left + size.w,
  10452. bottom: y + size.h
  10453. };
  10454. }
  10455. function isNotOverlapped(item, area) {
  10456. if (!area) {
  10457. return true;
  10458. }
  10459. const { left , top , right , bottom } = item;
  10460. const apexesInArea = _isPointInArea({
  10461. x: left,
  10462. y: top
  10463. }, area) || _isPointInArea({
  10464. x: left,
  10465. y: bottom
  10466. }, area) || _isPointInArea({
  10467. x: right,
  10468. y: top
  10469. }, area) || _isPointInArea({
  10470. x: right,
  10471. y: bottom
  10472. }, area);
  10473. return !apexesInArea;
  10474. }
  10475. function buildPointLabelItems(scale, labelSizes, padding) {
  10476. const items = [];
  10477. const valueCount = scale._pointLabels.length;
  10478. const opts = scale.options;
  10479. const { centerPointLabels , display } = opts.pointLabels;
  10480. const itemOpts = {
  10481. extra: getTickBackdropHeight(opts) / 2,
  10482. additionalAngle: centerPointLabels ? PI / valueCount : 0
  10483. };
  10484. let area;
  10485. for(let i = 0; i < valueCount; i++){
  10486. itemOpts.padding = padding[i];
  10487. itemOpts.size = labelSizes[i];
  10488. const item = createPointLabelItem(scale, i, itemOpts);
  10489. items.push(item);
  10490. if (display === 'auto') {
  10491. item.visible = isNotOverlapped(item, area);
  10492. if (item.visible) {
  10493. area = item;
  10494. }
  10495. }
  10496. }
  10497. return items;
  10498. }
  10499. function getTextAlignForAngle(angle) {
  10500. if (angle === 0 || angle === 180) {
  10501. return 'center';
  10502. } else if (angle < 180) {
  10503. return 'left';
  10504. }
  10505. return 'right';
  10506. }
  10507. function leftForTextAlign(x, w, align) {
  10508. if (align === 'right') {
  10509. x -= w;
  10510. } else if (align === 'center') {
  10511. x -= w / 2;
  10512. }
  10513. return x;
  10514. }
  10515. function yForAngle(y, h, angle) {
  10516. if (angle === 90 || angle === 270) {
  10517. y -= h / 2;
  10518. } else if (angle > 270 || angle < 90) {
  10519. y -= h;
  10520. }
  10521. return y;
  10522. }
  10523. function drawPointLabelBox(ctx, opts, item) {
  10524. const { left , top , right , bottom } = item;
  10525. const { backdropColor } = opts;
  10526. if (!isNullOrUndef(backdropColor)) {
  10527. const borderRadius = toTRBLCorners(opts.borderRadius);
  10528. const padding = toPadding(opts.backdropPadding);
  10529. ctx.fillStyle = backdropColor;
  10530. const backdropLeft = left - padding.left;
  10531. const backdropTop = top - padding.top;
  10532. const backdropWidth = right - left + padding.width;
  10533. const backdropHeight = bottom - top + padding.height;
  10534. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  10535. ctx.beginPath();
  10536. addRoundedRectPath(ctx, {
  10537. x: backdropLeft,
  10538. y: backdropTop,
  10539. w: backdropWidth,
  10540. h: backdropHeight,
  10541. radius: borderRadius
  10542. });
  10543. ctx.fill();
  10544. } else {
  10545. ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
  10546. }
  10547. }
  10548. }
  10549. function drawPointLabels(scale, labelCount) {
  10550. const { ctx , options: { pointLabels } } = scale;
  10551. for(let i = labelCount - 1; i >= 0; i--){
  10552. const item = scale._pointLabelItems[i];
  10553. if (!item.visible) {
  10554. continue;
  10555. }
  10556. const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
  10557. drawPointLabelBox(ctx, optsAtIndex, item);
  10558. const plFont = toFont(optsAtIndex.font);
  10559. const { x , y , textAlign } = item;
  10560. renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
  10561. color: optsAtIndex.color,
  10562. textAlign: textAlign,
  10563. textBaseline: 'middle'
  10564. });
  10565. }
  10566. }
  10567. function pathRadiusLine(scale, radius, circular, labelCount) {
  10568. const { ctx } = scale;
  10569. if (circular) {
  10570. ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
  10571. } else {
  10572. let pointPosition = scale.getPointPosition(0, radius);
  10573. ctx.moveTo(pointPosition.x, pointPosition.y);
  10574. for(let i = 1; i < labelCount; i++){
  10575. pointPosition = scale.getPointPosition(i, radius);
  10576. ctx.lineTo(pointPosition.x, pointPosition.y);
  10577. }
  10578. }
  10579. }
  10580. function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
  10581. const ctx = scale.ctx;
  10582. const circular = gridLineOpts.circular;
  10583. const { color , lineWidth } = gridLineOpts;
  10584. if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
  10585. return;
  10586. }
  10587. ctx.save();
  10588. ctx.strokeStyle = color;
  10589. ctx.lineWidth = lineWidth;
  10590. ctx.setLineDash(borderOpts.dash);
  10591. ctx.lineDashOffset = borderOpts.dashOffset;
  10592. ctx.beginPath();
  10593. pathRadiusLine(scale, radius, circular, labelCount);
  10594. ctx.closePath();
  10595. ctx.stroke();
  10596. ctx.restore();
  10597. }
  10598. function createPointLabelContext(parent, index, label) {
  10599. return createContext(parent, {
  10600. label,
  10601. index,
  10602. type: 'pointLabel'
  10603. });
  10604. }
  10605. class RadialLinearScale extends LinearScaleBase {
  10606. static id = 'radialLinear';
  10607. static defaults = {
  10608. display: true,
  10609. animate: true,
  10610. position: 'chartArea',
  10611. angleLines: {
  10612. display: true,
  10613. lineWidth: 1,
  10614. borderDash: [],
  10615. borderDashOffset: 0.0
  10616. },
  10617. grid: {
  10618. circular: false
  10619. },
  10620. startAngle: 0,
  10621. ticks: {
  10622. showLabelBackdrop: true,
  10623. callback: Ticks.formatters.numeric
  10624. },
  10625. pointLabels: {
  10626. backdropColor: undefined,
  10627. backdropPadding: 2,
  10628. display: true,
  10629. font: {
  10630. size: 10
  10631. },
  10632. callback (label) {
  10633. return label;
  10634. },
  10635. padding: 5,
  10636. centerPointLabels: false
  10637. }
  10638. };
  10639. static defaultRoutes = {
  10640. 'angleLines.color': 'borderColor',
  10641. 'pointLabels.color': 'color',
  10642. 'ticks.color': 'color'
  10643. };
  10644. static descriptors = {
  10645. angleLines: {
  10646. _fallback: 'grid'
  10647. }
  10648. };
  10649. constructor(cfg){
  10650. super(cfg);
  10651. this.xCenter = undefined;
  10652. this.yCenter = undefined;
  10653. this.drawingArea = undefined;
  10654. this._pointLabels = [];
  10655. this._pointLabelItems = [];
  10656. }
  10657. setDimensions() {
  10658. const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
  10659. const w = this.width = this.maxWidth - padding.width;
  10660. const h = this.height = this.maxHeight - padding.height;
  10661. this.xCenter = Math.floor(this.left + w / 2 + padding.left);
  10662. this.yCenter = Math.floor(this.top + h / 2 + padding.top);
  10663. this.drawingArea = Math.floor(Math.min(w, h) / 2);
  10664. }
  10665. determineDataLimits() {
  10666. const { min , max } = this.getMinMax(false);
  10667. this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
  10668. this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
  10669. this.handleTickRangeOptions();
  10670. }
  10671. computeTickLimit() {
  10672. return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  10673. }
  10674. generateTickLabels(ticks) {
  10675. LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
  10676. this._pointLabels = this.getLabels().map((value, index)=>{
  10677. const label = callback(this.options.pointLabels.callback, [
  10678. value,
  10679. index
  10680. ], this);
  10681. return label || label === 0 ? label : '';
  10682. }).filter((v, i)=>this.chart.getDataVisibility(i));
  10683. }
  10684. fit() {
  10685. const opts = this.options;
  10686. if (opts.display && opts.pointLabels.display) {
  10687. fitWithPointLabels(this);
  10688. } else {
  10689. this.setCenterPoint(0, 0, 0, 0);
  10690. }
  10691. }
  10692. setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
  10693. this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
  10694. this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
  10695. this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
  10696. }
  10697. getIndexAngle(index) {
  10698. const angleMultiplier = TAU / (this._pointLabels.length || 1);
  10699. const startAngle = this.options.startAngle || 0;
  10700. return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
  10701. }
  10702. getDistanceFromCenterForValue(value) {
  10703. if (isNullOrUndef(value)) {
  10704. return NaN;
  10705. }
  10706. const scalingFactor = this.drawingArea / (this.max - this.min);
  10707. if (this.options.reverse) {
  10708. return (this.max - value) * scalingFactor;
  10709. }
  10710. return (value - this.min) * scalingFactor;
  10711. }
  10712. getValueForDistanceFromCenter(distance) {
  10713. if (isNullOrUndef(distance)) {
  10714. return NaN;
  10715. }
  10716. const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
  10717. return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
  10718. }
  10719. getPointLabelContext(index) {
  10720. const pointLabels = this._pointLabels || [];
  10721. if (index >= 0 && index < pointLabels.length) {
  10722. const pointLabel = pointLabels[index];
  10723. return createPointLabelContext(this.getContext(), index, pointLabel);
  10724. }
  10725. }
  10726. getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
  10727. const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
  10728. return {
  10729. x: Math.cos(angle) * distanceFromCenter + this.xCenter,
  10730. y: Math.sin(angle) * distanceFromCenter + this.yCenter,
  10731. angle
  10732. };
  10733. }
  10734. getPointPositionForValue(index, value) {
  10735. return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  10736. }
  10737. getBasePosition(index) {
  10738. return this.getPointPositionForValue(index || 0, this.getBaseValue());
  10739. }
  10740. getPointLabelPosition(index) {
  10741. const { left , top , right , bottom } = this._pointLabelItems[index];
  10742. return {
  10743. left,
  10744. top,
  10745. right,
  10746. bottom
  10747. };
  10748. }
  10749. drawBackground() {
  10750. const { backgroundColor , grid: { circular } } = this.options;
  10751. if (backgroundColor) {
  10752. const ctx = this.ctx;
  10753. ctx.save();
  10754. ctx.beginPath();
  10755. pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
  10756. ctx.closePath();
  10757. ctx.fillStyle = backgroundColor;
  10758. ctx.fill();
  10759. ctx.restore();
  10760. }
  10761. }
  10762. drawGrid() {
  10763. const ctx = this.ctx;
  10764. const opts = this.options;
  10765. const { angleLines , grid , border } = opts;
  10766. const labelCount = this._pointLabels.length;
  10767. let i, offset, position;
  10768. if (opts.pointLabels.display) {
  10769. drawPointLabels(this, labelCount);
  10770. }
  10771. if (grid.display) {
  10772. this.ticks.forEach((tick, index)=>{
  10773. if (index !== 0) {
  10774. offset = this.getDistanceFromCenterForValue(tick.value);
  10775. const context = this.getContext(index);
  10776. const optsAtIndex = grid.setContext(context);
  10777. const optsAtIndexBorder = border.setContext(context);
  10778. drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
  10779. }
  10780. });
  10781. }
  10782. if (angleLines.display) {
  10783. ctx.save();
  10784. for(i = labelCount - 1; i >= 0; i--){
  10785. const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
  10786. const { color , lineWidth } = optsAtIndex;
  10787. if (!lineWidth || !color) {
  10788. continue;
  10789. }
  10790. ctx.lineWidth = lineWidth;
  10791. ctx.strokeStyle = color;
  10792. ctx.setLineDash(optsAtIndex.borderDash);
  10793. ctx.lineDashOffset = optsAtIndex.borderDashOffset;
  10794. offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
  10795. position = this.getPointPosition(i, offset);
  10796. ctx.beginPath();
  10797. ctx.moveTo(this.xCenter, this.yCenter);
  10798. ctx.lineTo(position.x, position.y);
  10799. ctx.stroke();
  10800. }
  10801. ctx.restore();
  10802. }
  10803. }
  10804. drawBorder() {}
  10805. drawLabels() {
  10806. const ctx = this.ctx;
  10807. const opts = this.options;
  10808. const tickOpts = opts.ticks;
  10809. if (!tickOpts.display) {
  10810. return;
  10811. }
  10812. const startAngle = this.getIndexAngle(0);
  10813. let offset, width;
  10814. ctx.save();
  10815. ctx.translate(this.xCenter, this.yCenter);
  10816. ctx.rotate(startAngle);
  10817. ctx.textAlign = 'center';
  10818. ctx.textBaseline = 'middle';
  10819. this.ticks.forEach((tick, index)=>{
  10820. if (index === 0 && !opts.reverse) {
  10821. return;
  10822. }
  10823. const optsAtIndex = tickOpts.setContext(this.getContext(index));
  10824. const tickFont = toFont(optsAtIndex.font);
  10825. offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
  10826. if (optsAtIndex.showLabelBackdrop) {
  10827. ctx.font = tickFont.string;
  10828. width = ctx.measureText(tick.label).width;
  10829. ctx.fillStyle = optsAtIndex.backdropColor;
  10830. const padding = toPadding(optsAtIndex.backdropPadding);
  10831. ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
  10832. }
  10833. renderText(ctx, tick.label, 0, -offset, tickFont, {
  10834. color: optsAtIndex.color,
  10835. strokeColor: optsAtIndex.textStrokeColor,
  10836. strokeWidth: optsAtIndex.textStrokeWidth
  10837. });
  10838. });
  10839. ctx.restore();
  10840. }
  10841. drawTitle() {}
  10842. }
  10843. const INTERVALS = {
  10844. millisecond: {
  10845. common: true,
  10846. size: 1,
  10847. steps: 1000
  10848. },
  10849. second: {
  10850. common: true,
  10851. size: 1000,
  10852. steps: 60
  10853. },
  10854. minute: {
  10855. common: true,
  10856. size: 60000,
  10857. steps: 60
  10858. },
  10859. hour: {
  10860. common: true,
  10861. size: 3600000,
  10862. steps: 24
  10863. },
  10864. day: {
  10865. common: true,
  10866. size: 86400000,
  10867. steps: 30
  10868. },
  10869. week: {
  10870. common: false,
  10871. size: 604800000,
  10872. steps: 4
  10873. },
  10874. month: {
  10875. common: true,
  10876. size: 2.628e9,
  10877. steps: 12
  10878. },
  10879. quarter: {
  10880. common: false,
  10881. size: 7.884e9,
  10882. steps: 4
  10883. },
  10884. year: {
  10885. common: true,
  10886. size: 3.154e10
  10887. }
  10888. };
  10889. const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
  10890. function sorter(a, b) {
  10891. return a - b;
  10892. }
  10893. function parse(scale, input) {
  10894. if (isNullOrUndef(input)) {
  10895. return null;
  10896. }
  10897. const adapter = scale._adapter;
  10898. const { parser , round , isoWeekday } = scale._parseOpts;
  10899. let value = input;
  10900. if (typeof parser === 'function') {
  10901. value = parser(value);
  10902. }
  10903. if (!isNumberFinite(value)) {
  10904. value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
  10905. }
  10906. if (value === null) {
  10907. return null;
  10908. }
  10909. if (round) {
  10910. value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
  10911. }
  10912. return +value;
  10913. }
  10914. function determineUnitForAutoTicks(minUnit, min, max, capacity) {
  10915. const ilen = UNITS.length;
  10916. for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
  10917. const interval = INTERVALS[UNITS[i]];
  10918. const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
  10919. if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
  10920. return UNITS[i];
  10921. }
  10922. }
  10923. return UNITS[ilen - 1];
  10924. }
  10925. function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
  10926. for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
  10927. const unit = UNITS[i];
  10928. if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
  10929. return unit;
  10930. }
  10931. }
  10932. return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  10933. }
  10934. function determineMajorUnit(unit) {
  10935. for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
  10936. if (INTERVALS[UNITS[i]].common) {
  10937. return UNITS[i];
  10938. }
  10939. }
  10940. }
  10941. function addTick(ticks, time, timestamps) {
  10942. if (!timestamps) {
  10943. ticks[time] = true;
  10944. } else if (timestamps.length) {
  10945. const { lo , hi } = _lookup(timestamps, time);
  10946. const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
  10947. ticks[timestamp] = true;
  10948. }
  10949. }
  10950. function setMajorTicks(scale, ticks, map, majorUnit) {
  10951. const adapter = scale._adapter;
  10952. const first = +adapter.startOf(ticks[0].value, majorUnit);
  10953. const last = ticks[ticks.length - 1].value;
  10954. let major, index;
  10955. for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
  10956. index = map[major];
  10957. if (index >= 0) {
  10958. ticks[index].major = true;
  10959. }
  10960. }
  10961. return ticks;
  10962. }
  10963. function ticksFromTimestamps(scale, values, majorUnit) {
  10964. const ticks = [];
  10965. const map = {};
  10966. const ilen = values.length;
  10967. let i, value;
  10968. for(i = 0; i < ilen; ++i){
  10969. value = values[i];
  10970. map[value] = i;
  10971. ticks.push({
  10972. value,
  10973. major: false
  10974. });
  10975. }
  10976. return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  10977. }
  10978. class TimeScale extends Scale {
  10979. static id = 'time';
  10980. static defaults = {
  10981. bounds: 'data',
  10982. adapters: {},
  10983. time: {
  10984. parser: false,
  10985. unit: false,
  10986. round: false,
  10987. isoWeekday: false,
  10988. minUnit: 'millisecond',
  10989. displayFormats: {}
  10990. },
  10991. ticks: {
  10992. source: 'auto',
  10993. callback: false,
  10994. major: {
  10995. enabled: false
  10996. }
  10997. }
  10998. };
  10999. constructor(props){
  11000. super(props);
  11001. this._cache = {
  11002. data: [],
  11003. labels: [],
  11004. all: []
  11005. };
  11006. this._unit = 'day';
  11007. this._majorUnit = undefined;
  11008. this._offsets = {};
  11009. this._normalized = false;
  11010. this._parseOpts = undefined;
  11011. }
  11012. init(scaleOpts, opts = {}) {
  11013. const time = scaleOpts.time || (scaleOpts.time = {});
  11014. const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
  11015. adapter.init(opts);
  11016. mergeIf(time.displayFormats, adapter.formats());
  11017. this._parseOpts = {
  11018. parser: time.parser,
  11019. round: time.round,
  11020. isoWeekday: time.isoWeekday
  11021. };
  11022. super.init(scaleOpts);
  11023. this._normalized = opts.normalized;
  11024. }
  11025. parse(raw, index) {
  11026. if (raw === undefined) {
  11027. return null;
  11028. }
  11029. return parse(this, raw);
  11030. }
  11031. beforeLayout() {
  11032. super.beforeLayout();
  11033. this._cache = {
  11034. data: [],
  11035. labels: [],
  11036. all: []
  11037. };
  11038. }
  11039. determineDataLimits() {
  11040. const options = this.options;
  11041. const adapter = this._adapter;
  11042. const unit = options.time.unit || 'day';
  11043. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  11044. function _applyBounds(bounds) {
  11045. if (!minDefined && !isNaN(bounds.min)) {
  11046. min = Math.min(min, bounds.min);
  11047. }
  11048. if (!maxDefined && !isNaN(bounds.max)) {
  11049. max = Math.max(max, bounds.max);
  11050. }
  11051. }
  11052. if (!minDefined || !maxDefined) {
  11053. _applyBounds(this._getLabelBounds());
  11054. if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
  11055. _applyBounds(this.getMinMax(false));
  11056. }
  11057. }
  11058. min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
  11059. max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
  11060. this.min = Math.min(min, max - 1);
  11061. this.max = Math.max(min + 1, max);
  11062. }
  11063. _getLabelBounds() {
  11064. const arr = this.getLabelTimestamps();
  11065. let min = Number.POSITIVE_INFINITY;
  11066. let max = Number.NEGATIVE_INFINITY;
  11067. if (arr.length) {
  11068. min = arr[0];
  11069. max = arr[arr.length - 1];
  11070. }
  11071. return {
  11072. min,
  11073. max
  11074. };
  11075. }
  11076. buildTicks() {
  11077. const options = this.options;
  11078. const timeOpts = options.time;
  11079. const tickOpts = options.ticks;
  11080. const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
  11081. if (options.bounds === 'ticks' && timestamps.length) {
  11082. this.min = this._userMin || timestamps[0];
  11083. this.max = this._userMax || timestamps[timestamps.length - 1];
  11084. }
  11085. const min = this.min;
  11086. const max = this.max;
  11087. const ticks = _filterBetween(timestamps, min, max);
  11088. this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
  11089. this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
  11090. this.initOffsets(timestamps);
  11091. if (options.reverse) {
  11092. ticks.reverse();
  11093. }
  11094. return ticksFromTimestamps(this, ticks, this._majorUnit);
  11095. }
  11096. afterAutoSkip() {
  11097. if (this.options.offsetAfterAutoskip) {
  11098. this.initOffsets(this.ticks.map((tick)=>+tick.value));
  11099. }
  11100. }
  11101. initOffsets(timestamps = []) {
  11102. let start = 0;
  11103. let end = 0;
  11104. let first, last;
  11105. if (this.options.offset && timestamps.length) {
  11106. first = this.getDecimalForValue(timestamps[0]);
  11107. if (timestamps.length === 1) {
  11108. start = 1 - first;
  11109. } else {
  11110. start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
  11111. }
  11112. last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
  11113. if (timestamps.length === 1) {
  11114. end = last;
  11115. } else {
  11116. end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
  11117. }
  11118. }
  11119. const limit = timestamps.length < 3 ? 0.5 : 0.25;
  11120. start = _limitValue(start, 0, limit);
  11121. end = _limitValue(end, 0, limit);
  11122. this._offsets = {
  11123. start,
  11124. end,
  11125. factor: 1 / (start + 1 + end)
  11126. };
  11127. }
  11128. _generate() {
  11129. const adapter = this._adapter;
  11130. const min = this.min;
  11131. const max = this.max;
  11132. const options = this.options;
  11133. const timeOpts = options.time;
  11134. const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
  11135. const stepSize = valueOrDefault(options.ticks.stepSize, 1);
  11136. const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
  11137. const hasWeekday = isNumber(weekday) || weekday === true;
  11138. const ticks = {};
  11139. let first = min;
  11140. let time, count;
  11141. if (hasWeekday) {
  11142. first = +adapter.startOf(first, 'isoWeek', weekday);
  11143. }
  11144. first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
  11145. if (adapter.diff(max, min, minor) > 100000 * stepSize) {
  11146. throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
  11147. }
  11148. const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
  11149. for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
  11150. addTick(ticks, time, timestamps);
  11151. }
  11152. if (time === max || options.bounds === 'ticks' || count === 1) {
  11153. addTick(ticks, time, timestamps);
  11154. }
  11155. return Object.keys(ticks).sort(sorter).map((x)=>+x);
  11156. }
  11157. getLabelForValue(value) {
  11158. const adapter = this._adapter;
  11159. const timeOpts = this.options.time;
  11160. if (timeOpts.tooltipFormat) {
  11161. return adapter.format(value, timeOpts.tooltipFormat);
  11162. }
  11163. return adapter.format(value, timeOpts.displayFormats.datetime);
  11164. }
  11165. format(value, format) {
  11166. const options = this.options;
  11167. const formats = options.time.displayFormats;
  11168. const unit = this._unit;
  11169. const fmt = format || formats[unit];
  11170. return this._adapter.format(value, fmt);
  11171. }
  11172. _tickFormatFunction(time, index, ticks, format) {
  11173. const options = this.options;
  11174. const formatter = options.ticks.callback;
  11175. if (formatter) {
  11176. return callback(formatter, [
  11177. time,
  11178. index,
  11179. ticks
  11180. ], this);
  11181. }
  11182. const formats = options.time.displayFormats;
  11183. const unit = this._unit;
  11184. const majorUnit = this._majorUnit;
  11185. const minorFormat = unit && formats[unit];
  11186. const majorFormat = majorUnit && formats[majorUnit];
  11187. const tick = ticks[index];
  11188. const major = majorUnit && majorFormat && tick && tick.major;
  11189. return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
  11190. }
  11191. generateTickLabels(ticks) {
  11192. let i, ilen, tick;
  11193. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  11194. tick = ticks[i];
  11195. tick.label = this._tickFormatFunction(tick.value, i, ticks);
  11196. }
  11197. }
  11198. getDecimalForValue(value) {
  11199. return value === null ? NaN : (value - this.min) / (this.max - this.min);
  11200. }
  11201. getPixelForValue(value) {
  11202. const offsets = this._offsets;
  11203. const pos = this.getDecimalForValue(value);
  11204. return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
  11205. }
  11206. getValueForPixel(pixel) {
  11207. const offsets = this._offsets;
  11208. const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11209. return this.min + pos * (this.max - this.min);
  11210. }
  11211. _getLabelSize(label) {
  11212. const ticksOpts = this.options.ticks;
  11213. const tickLabelWidth = this.ctx.measureText(label).width;
  11214. const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
  11215. const cosRotation = Math.cos(angle);
  11216. const sinRotation = Math.sin(angle);
  11217. const tickFontSize = this._resolveTickFontOptions(0).size;
  11218. return {
  11219. w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
  11220. h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
  11221. };
  11222. }
  11223. _getLabelCapacity(exampleTime) {
  11224. const timeOpts = this.options.time;
  11225. const displayFormats = timeOpts.displayFormats;
  11226. const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
  11227. const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
  11228. exampleTime
  11229. ], this._majorUnit), format);
  11230. const size = this._getLabelSize(exampleLabel);
  11231. const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
  11232. return capacity > 0 ? capacity : 1;
  11233. }
  11234. getDataTimestamps() {
  11235. let timestamps = this._cache.data || [];
  11236. let i, ilen;
  11237. if (timestamps.length) {
  11238. return timestamps;
  11239. }
  11240. const metas = this.getMatchingVisibleMetas();
  11241. if (this._normalized && metas.length) {
  11242. return this._cache.data = metas[0].controller.getAllParsedValues(this);
  11243. }
  11244. for(i = 0, ilen = metas.length; i < ilen; ++i){
  11245. timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
  11246. }
  11247. return this._cache.data = this.normalize(timestamps);
  11248. }
  11249. getLabelTimestamps() {
  11250. const timestamps = this._cache.labels || [];
  11251. let i, ilen;
  11252. if (timestamps.length) {
  11253. return timestamps;
  11254. }
  11255. const labels = this.getLabels();
  11256. for(i = 0, ilen = labels.length; i < ilen; ++i){
  11257. timestamps.push(parse(this, labels[i]));
  11258. }
  11259. return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
  11260. }
  11261. normalize(values) {
  11262. return _arrayUnique(values.sort(sorter));
  11263. }
  11264. }
  11265. function interpolate(table, val, reverse) {
  11266. let lo = 0;
  11267. let hi = table.length - 1;
  11268. let prevSource, nextSource, prevTarget, nextTarget;
  11269. if (reverse) {
  11270. if (val >= table[lo].pos && val <= table[hi].pos) {
  11271. ({ lo , hi } = _lookupByKey(table, 'pos', val));
  11272. }
  11273. ({ pos: prevSource , time: prevTarget } = table[lo]);
  11274. ({ pos: nextSource , time: nextTarget } = table[hi]);
  11275. } else {
  11276. if (val >= table[lo].time && val <= table[hi].time) {
  11277. ({ lo , hi } = _lookupByKey(table, 'time', val));
  11278. }
  11279. ({ time: prevSource , pos: prevTarget } = table[lo]);
  11280. ({ time: nextSource , pos: nextTarget } = table[hi]);
  11281. }
  11282. const span = nextSource - prevSource;
  11283. return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  11284. }
  11285. class TimeSeriesScale extends TimeScale {
  11286. static id = 'timeseries';
  11287. static defaults = TimeScale.defaults;
  11288. constructor(props){
  11289. super(props);
  11290. this._table = [];
  11291. this._minPos = undefined;
  11292. this._tableRange = undefined;
  11293. }
  11294. initOffsets() {
  11295. const timestamps = this._getTimestampsForTable();
  11296. const table = this._table = this.buildLookupTable(timestamps);
  11297. this._minPos = interpolate(table, this.min);
  11298. this._tableRange = interpolate(table, this.max) - this._minPos;
  11299. super.initOffsets(timestamps);
  11300. }
  11301. buildLookupTable(timestamps) {
  11302. const { min , max } = this;
  11303. const items = [];
  11304. const table = [];
  11305. let i, ilen, prev, curr, next;
  11306. for(i = 0, ilen = timestamps.length; i < ilen; ++i){
  11307. curr = timestamps[i];
  11308. if (curr >= min && curr <= max) {
  11309. items.push(curr);
  11310. }
  11311. }
  11312. if (items.length < 2) {
  11313. return [
  11314. {
  11315. time: min,
  11316. pos: 0
  11317. },
  11318. {
  11319. time: max,
  11320. pos: 1
  11321. }
  11322. ];
  11323. }
  11324. for(i = 0, ilen = items.length; i < ilen; ++i){
  11325. next = items[i + 1];
  11326. prev = items[i - 1];
  11327. curr = items[i];
  11328. if (Math.round((next + prev) / 2) !== curr) {
  11329. table.push({
  11330. time: curr,
  11331. pos: i / (ilen - 1)
  11332. });
  11333. }
  11334. }
  11335. return table;
  11336. }
  11337. _generate() {
  11338. const min = this.min;
  11339. const max = this.max;
  11340. let timestamps = super.getDataTimestamps();
  11341. if (!timestamps.includes(min) || !timestamps.length) {
  11342. timestamps.splice(0, 0, min);
  11343. }
  11344. if (!timestamps.includes(max) || timestamps.length === 1) {
  11345. timestamps.push(max);
  11346. }
  11347. return timestamps.sort((a, b)=>a - b);
  11348. }
  11349. _getTimestampsForTable() {
  11350. let timestamps = this._cache.all || [];
  11351. if (timestamps.length) {
  11352. return timestamps;
  11353. }
  11354. const data = this.getDataTimestamps();
  11355. const label = this.getLabelTimestamps();
  11356. if (data.length && label.length) {
  11357. timestamps = this.normalize(data.concat(label));
  11358. } else {
  11359. timestamps = data.length ? data : label;
  11360. }
  11361. timestamps = this._cache.all = timestamps;
  11362. return timestamps;
  11363. }
  11364. getDecimalForValue(value) {
  11365. return (interpolate(this._table, value) - this._minPos) / this._tableRange;
  11366. }
  11367. getValueForPixel(pixel) {
  11368. const offsets = this._offsets;
  11369. const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11370. return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
  11371. }
  11372. }
  11373. var scales = /*#__PURE__*/Object.freeze({
  11374. __proto__: null,
  11375. CategoryScale: CategoryScale,
  11376. LinearScale: LinearScale,
  11377. LogarithmicScale: LogarithmicScale,
  11378. RadialLinearScale: RadialLinearScale,
  11379. TimeScale: TimeScale,
  11380. TimeSeriesScale: TimeSeriesScale
  11381. });
  11382. const registerables = [
  11383. controllers,
  11384. elements,
  11385. plugins,
  11386. scales
  11387. ];
  11388. export { Animation, Animations, ArcElement, BarController, BarElement, BasePlatform, BasicPlatform, BubbleController, CategoryScale, Chart, plugin_colors as Colors, DatasetController, plugin_decimation as Decimation, DomPlatform, DoughnutController, Element, index as Filler, Interaction, plugin_legend as Legend, LineController, LineElement, LinearScale, LogarithmicScale, PieController, PointElement, PolarAreaController, RadarController, RadialLinearScale, Scale, ScatterController, plugin_subtitle as SubTitle, Ticks, TimeScale, TimeSeriesScale, plugin_title as Title, plugin_tooltip as Tooltip, adapters as _adapters, _detectPlatform, animator, controllers, defaults, elements, layouts, plugins, registerables, registry, scales };
  11389. //# sourceMappingURL=chart.js.map