暫無描述

chart.cjs 400KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497
  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. 'use strict';
  8. var helpers_segment = require('./chunks/helpers.segment.cjs');
  9. require('@kurkle/color');
  10. class Animator {
  11. constructor(){
  12. this._request = null;
  13. this._charts = new Map();
  14. this._running = false;
  15. this._lastDate = undefined;
  16. }
  17. _notify(chart, anims, date, type) {
  18. const callbacks = anims.listeners[type];
  19. const numSteps = anims.duration;
  20. callbacks.forEach((fn)=>fn({
  21. chart,
  22. initial: anims.initial,
  23. numSteps,
  24. currentStep: Math.min(date - anims.start, numSteps)
  25. }));
  26. }
  27. _refresh() {
  28. if (this._request) {
  29. return;
  30. }
  31. this._running = true;
  32. this._request = helpers_segment.requestAnimFrame.call(window, ()=>{
  33. this._update();
  34. this._request = null;
  35. if (this._running) {
  36. this._refresh();
  37. }
  38. });
  39. }
  40. _update(date = Date.now()) {
  41. let remaining = 0;
  42. this._charts.forEach((anims, chart)=>{
  43. if (!anims.running || !anims.items.length) {
  44. return;
  45. }
  46. const items = anims.items;
  47. let i = items.length - 1;
  48. let draw = false;
  49. let item;
  50. for(; i >= 0; --i){
  51. item = items[i];
  52. if (item._active) {
  53. if (item._total > anims.duration) {
  54. anims.duration = item._total;
  55. }
  56. item.tick(date);
  57. draw = true;
  58. } else {
  59. items[i] = items[items.length - 1];
  60. items.pop();
  61. }
  62. }
  63. if (draw) {
  64. chart.draw();
  65. this._notify(chart, anims, date, 'progress');
  66. }
  67. if (!items.length) {
  68. anims.running = false;
  69. this._notify(chart, anims, date, 'complete');
  70. anims.initial = false;
  71. }
  72. remaining += items.length;
  73. });
  74. this._lastDate = date;
  75. if (remaining === 0) {
  76. this._running = false;
  77. }
  78. }
  79. _getAnims(chart) {
  80. const charts = this._charts;
  81. let anims = charts.get(chart);
  82. if (!anims) {
  83. anims = {
  84. running: false,
  85. initial: true,
  86. items: [],
  87. listeners: {
  88. complete: [],
  89. progress: []
  90. }
  91. };
  92. charts.set(chart, anims);
  93. }
  94. return anims;
  95. }
  96. listen(chart, event, cb) {
  97. this._getAnims(chart).listeners[event].push(cb);
  98. }
  99. add(chart, items) {
  100. if (!items || !items.length) {
  101. return;
  102. }
  103. this._getAnims(chart).items.push(...items);
  104. }
  105. has(chart) {
  106. return this._getAnims(chart).items.length > 0;
  107. }
  108. start(chart) {
  109. const anims = this._charts.get(chart);
  110. if (!anims) {
  111. return;
  112. }
  113. anims.running = true;
  114. anims.start = Date.now();
  115. anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
  116. this._refresh();
  117. }
  118. running(chart) {
  119. if (!this._running) {
  120. return false;
  121. }
  122. const anims = this._charts.get(chart);
  123. if (!anims || !anims.running || !anims.items.length) {
  124. return false;
  125. }
  126. return true;
  127. }
  128. stop(chart) {
  129. const anims = this._charts.get(chart);
  130. if (!anims || !anims.items.length) {
  131. return;
  132. }
  133. const items = anims.items;
  134. let i = items.length - 1;
  135. for(; i >= 0; --i){
  136. items[i].cancel();
  137. }
  138. anims.items = [];
  139. this._notify(chart, anims, Date.now(), 'complete');
  140. }
  141. remove(chart) {
  142. return this._charts.delete(chart);
  143. }
  144. }
  145. var animator = /* #__PURE__ */ new Animator();
  146. const transparent = 'transparent';
  147. const interpolators = {
  148. boolean (from, to, factor) {
  149. return factor > 0.5 ? to : from;
  150. },
  151. color (from, to, factor) {
  152. const c0 = helpers_segment.color(from || transparent);
  153. const c1 = c0.valid && helpers_segment.color(to || transparent);
  154. return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
  155. },
  156. number (from, to, factor) {
  157. return from + (to - from) * factor;
  158. }
  159. };
  160. class Animation {
  161. constructor(cfg, target, prop, to){
  162. const currentValue = target[prop];
  163. to = helpers_segment.resolve([
  164. cfg.to,
  165. to,
  166. currentValue,
  167. cfg.from
  168. ]);
  169. const from = helpers_segment.resolve([
  170. cfg.from,
  171. currentValue,
  172. to
  173. ]);
  174. this._active = true;
  175. this._fn = cfg.fn || interpolators[cfg.type || typeof from];
  176. this._easing = helpers_segment.effects[cfg.easing] || helpers_segment.effects.linear;
  177. this._start = Math.floor(Date.now() + (cfg.delay || 0));
  178. this._duration = this._total = Math.floor(cfg.duration);
  179. this._loop = !!cfg.loop;
  180. this._target = target;
  181. this._prop = prop;
  182. this._from = from;
  183. this._to = to;
  184. this._promises = undefined;
  185. }
  186. active() {
  187. return this._active;
  188. }
  189. update(cfg, to, date) {
  190. if (this._active) {
  191. this._notify(false);
  192. const currentValue = this._target[this._prop];
  193. const elapsed = date - this._start;
  194. const remain = this._duration - elapsed;
  195. this._start = date;
  196. this._duration = Math.floor(Math.max(remain, cfg.duration));
  197. this._total += elapsed;
  198. this._loop = !!cfg.loop;
  199. this._to = helpers_segment.resolve([
  200. cfg.to,
  201. to,
  202. currentValue,
  203. cfg.from
  204. ]);
  205. this._from = helpers_segment.resolve([
  206. cfg.from,
  207. currentValue,
  208. to
  209. ]);
  210. }
  211. }
  212. cancel() {
  213. if (this._active) {
  214. this.tick(Date.now());
  215. this._active = false;
  216. this._notify(false);
  217. }
  218. }
  219. tick(date) {
  220. const elapsed = date - this._start;
  221. const duration = this._duration;
  222. const prop = this._prop;
  223. const from = this._from;
  224. const loop = this._loop;
  225. const to = this._to;
  226. let factor;
  227. this._active = from !== to && (loop || elapsed < duration);
  228. if (!this._active) {
  229. this._target[prop] = to;
  230. this._notify(true);
  231. return;
  232. }
  233. if (elapsed < 0) {
  234. this._target[prop] = from;
  235. return;
  236. }
  237. factor = elapsed / duration % 2;
  238. factor = loop && factor > 1 ? 2 - factor : factor;
  239. factor = this._easing(Math.min(1, Math.max(0, factor)));
  240. this._target[prop] = this._fn(from, to, factor);
  241. }
  242. wait() {
  243. const promises = this._promises || (this._promises = []);
  244. return new Promise((res, rej)=>{
  245. promises.push({
  246. res,
  247. rej
  248. });
  249. });
  250. }
  251. _notify(resolved) {
  252. const method = resolved ? 'res' : 'rej';
  253. const promises = this._promises || [];
  254. for(let i = 0; i < promises.length; i++){
  255. promises[i][method]();
  256. }
  257. }
  258. }
  259. class Animations {
  260. constructor(chart, config){
  261. this._chart = chart;
  262. this._properties = new Map();
  263. this.configure(config);
  264. }
  265. configure(config) {
  266. if (!helpers_segment.isObject(config)) {
  267. return;
  268. }
  269. const animationOptions = Object.keys(helpers_segment.defaults.animation);
  270. const animatedProps = this._properties;
  271. Object.getOwnPropertyNames(config).forEach((key)=>{
  272. const cfg = config[key];
  273. if (!helpers_segment.isObject(cfg)) {
  274. return;
  275. }
  276. const resolved = {};
  277. for (const option of animationOptions){
  278. resolved[option] = cfg[option];
  279. }
  280. (helpers_segment.isArray(cfg.properties) && cfg.properties || [
  281. key
  282. ]).forEach((prop)=>{
  283. if (prop === key || !animatedProps.has(prop)) {
  284. animatedProps.set(prop, resolved);
  285. }
  286. });
  287. });
  288. }
  289. _animateOptions(target, values) {
  290. const newOptions = values.options;
  291. const options = resolveTargetOptions(target, newOptions);
  292. if (!options) {
  293. return [];
  294. }
  295. const animations = this._createAnimations(options, newOptions);
  296. if (newOptions.$shared) {
  297. awaitAll(target.options.$animations, newOptions).then(()=>{
  298. target.options = newOptions;
  299. }, ()=>{
  300. });
  301. }
  302. return animations;
  303. }
  304. _createAnimations(target, values) {
  305. const animatedProps = this._properties;
  306. const animations = [];
  307. const running = target.$animations || (target.$animations = {});
  308. const props = Object.keys(values);
  309. const date = Date.now();
  310. let i;
  311. for(i = props.length - 1; i >= 0; --i){
  312. const prop = props[i];
  313. if (prop.charAt(0) === '$') {
  314. continue;
  315. }
  316. if (prop === 'options') {
  317. animations.push(...this._animateOptions(target, values));
  318. continue;
  319. }
  320. const value = values[prop];
  321. let animation = running[prop];
  322. const cfg = animatedProps.get(prop);
  323. if (animation) {
  324. if (cfg && animation.active()) {
  325. animation.update(cfg, value, date);
  326. continue;
  327. } else {
  328. animation.cancel();
  329. }
  330. }
  331. if (!cfg || !cfg.duration) {
  332. target[prop] = value;
  333. continue;
  334. }
  335. running[prop] = animation = new Animation(cfg, target, prop, value);
  336. animations.push(animation);
  337. }
  338. return animations;
  339. }
  340. update(target, values) {
  341. if (this._properties.size === 0) {
  342. Object.assign(target, values);
  343. return;
  344. }
  345. const animations = this._createAnimations(target, values);
  346. if (animations.length) {
  347. animator.add(this._chart, animations);
  348. return true;
  349. }
  350. }
  351. }
  352. function awaitAll(animations, properties) {
  353. const running = [];
  354. const keys = Object.keys(properties);
  355. for(let i = 0; i < keys.length; i++){
  356. const anim = animations[keys[i]];
  357. if (anim && anim.active()) {
  358. running.push(anim.wait());
  359. }
  360. }
  361. return Promise.all(running);
  362. }
  363. function resolveTargetOptions(target, newOptions) {
  364. if (!newOptions) {
  365. return;
  366. }
  367. let options = target.options;
  368. if (!options) {
  369. target.options = newOptions;
  370. return;
  371. }
  372. if (options.$shared) {
  373. target.options = options = Object.assign({}, options, {
  374. $shared: false,
  375. $animations: {}
  376. });
  377. }
  378. return options;
  379. }
  380. function scaleClip(scale, allowedOverflow) {
  381. const opts = scale && scale.options || {};
  382. const reverse = opts.reverse;
  383. const min = opts.min === undefined ? allowedOverflow : 0;
  384. const max = opts.max === undefined ? allowedOverflow : 0;
  385. return {
  386. start: reverse ? max : min,
  387. end: reverse ? min : max
  388. };
  389. }
  390. function defaultClip(xScale, yScale, allowedOverflow) {
  391. if (allowedOverflow === false) {
  392. return false;
  393. }
  394. const x = scaleClip(xScale, allowedOverflow);
  395. const y = scaleClip(yScale, allowedOverflow);
  396. return {
  397. top: y.end,
  398. right: x.end,
  399. bottom: y.start,
  400. left: x.start
  401. };
  402. }
  403. function toClip(value) {
  404. let t, r, b, l;
  405. if (helpers_segment.isObject(value)) {
  406. t = value.top;
  407. r = value.right;
  408. b = value.bottom;
  409. l = value.left;
  410. } else {
  411. t = r = b = l = value;
  412. }
  413. return {
  414. top: t,
  415. right: r,
  416. bottom: b,
  417. left: l,
  418. disabled: value === false
  419. };
  420. }
  421. function getSortedDatasetIndices(chart, filterVisible) {
  422. const keys = [];
  423. const metasets = chart._getSortedDatasetMetas(filterVisible);
  424. let i, ilen;
  425. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  426. keys.push(metasets[i].index);
  427. }
  428. return keys;
  429. }
  430. function applyStack(stack, value, dsIndex, options = {}) {
  431. const keys = stack.keys;
  432. const singleMode = options.mode === 'single';
  433. let i, ilen, datasetIndex, otherValue;
  434. if (value === null) {
  435. return;
  436. }
  437. for(i = 0, ilen = keys.length; i < ilen; ++i){
  438. datasetIndex = +keys[i];
  439. if (datasetIndex === dsIndex) {
  440. if (options.all) {
  441. continue;
  442. }
  443. break;
  444. }
  445. otherValue = stack.values[datasetIndex];
  446. if (helpers_segment.isNumberFinite(otherValue) && (singleMode || value === 0 || helpers_segment.sign(value) === helpers_segment.sign(otherValue))) {
  447. value += otherValue;
  448. }
  449. }
  450. return value;
  451. }
  452. function convertObjectDataToArray(data) {
  453. const keys = Object.keys(data);
  454. const adata = new Array(keys.length);
  455. let i, ilen, key;
  456. for(i = 0, ilen = keys.length; i < ilen; ++i){
  457. key = keys[i];
  458. adata[i] = {
  459. x: key,
  460. y: data[key]
  461. };
  462. }
  463. return adata;
  464. }
  465. function isStacked(scale, meta) {
  466. const stacked = scale && scale.options.stacked;
  467. return stacked || stacked === undefined && meta.stack !== undefined;
  468. }
  469. function getStackKey(indexScale, valueScale, meta) {
  470. return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
  471. }
  472. function getUserBounds(scale) {
  473. const { min , max , minDefined , maxDefined } = scale.getUserBounds();
  474. return {
  475. min: minDefined ? min : Number.NEGATIVE_INFINITY,
  476. max: maxDefined ? max : Number.POSITIVE_INFINITY
  477. };
  478. }
  479. function getOrCreateStack(stacks, stackKey, indexValue) {
  480. const subStack = stacks[stackKey] || (stacks[stackKey] = {});
  481. return subStack[indexValue] || (subStack[indexValue] = {});
  482. }
  483. function getLastIndexInStack(stack, vScale, positive, type) {
  484. for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
  485. const value = stack[meta.index];
  486. if (positive && value > 0 || !positive && value < 0) {
  487. return meta.index;
  488. }
  489. }
  490. return null;
  491. }
  492. function updateStacks(controller, parsed) {
  493. const { chart , _cachedMeta: meta } = controller;
  494. const stacks = chart._stacks || (chart._stacks = {});
  495. const { iScale , vScale , index: datasetIndex } = meta;
  496. const iAxis = iScale.axis;
  497. const vAxis = vScale.axis;
  498. const key = getStackKey(iScale, vScale, meta);
  499. const ilen = parsed.length;
  500. let stack;
  501. for(let i = 0; i < ilen; ++i){
  502. const item = parsed[i];
  503. const { [iAxis]: index , [vAxis]: value } = item;
  504. const itemStacks = item._stacks || (item._stacks = {});
  505. stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
  506. stack[datasetIndex] = value;
  507. stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
  508. stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
  509. const visualValues = stack._visualValues || (stack._visualValues = {});
  510. visualValues[datasetIndex] = value;
  511. }
  512. }
  513. function getFirstScaleId(chart, axis) {
  514. const scales = chart.scales;
  515. return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
  516. }
  517. function createDatasetContext(parent, index) {
  518. return helpers_segment.createContext(parent, {
  519. active: false,
  520. dataset: undefined,
  521. datasetIndex: index,
  522. index,
  523. mode: 'default',
  524. type: 'dataset'
  525. });
  526. }
  527. function createDataContext(parent, index, element) {
  528. return helpers_segment.createContext(parent, {
  529. active: false,
  530. dataIndex: index,
  531. parsed: undefined,
  532. raw: undefined,
  533. element,
  534. index,
  535. mode: 'default',
  536. type: 'data'
  537. });
  538. }
  539. function clearStacks(meta, items) {
  540. const datasetIndex = meta.controller.index;
  541. const axis = meta.vScale && meta.vScale.axis;
  542. if (!axis) {
  543. return;
  544. }
  545. items = items || meta._parsed;
  546. for (const parsed of items){
  547. const stacks = parsed._stacks;
  548. if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
  549. return;
  550. }
  551. delete stacks[axis][datasetIndex];
  552. if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
  553. delete stacks[axis]._visualValues[datasetIndex];
  554. }
  555. }
  556. }
  557. const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
  558. const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
  559. const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
  560. keys: getSortedDatasetIndices(chart, true),
  561. values: null
  562. };
  563. class DatasetController {
  564. static defaults = {};
  565. static datasetElementType = null;
  566. static dataElementType = null;
  567. constructor(chart, datasetIndex){
  568. this.chart = chart;
  569. this._ctx = chart.ctx;
  570. this.index = datasetIndex;
  571. this._cachedDataOpts = {};
  572. this._cachedMeta = this.getMeta();
  573. this._type = this._cachedMeta.type;
  574. this.options = undefined;
  575. this._parsing = false;
  576. this._data = undefined;
  577. this._objectData = undefined;
  578. this._sharedOptions = undefined;
  579. this._drawStart = undefined;
  580. this._drawCount = undefined;
  581. this.enableOptionSharing = false;
  582. this.supportsDecimation = false;
  583. this.$context = undefined;
  584. this._syncList = [];
  585. this.datasetElementType = new.target.datasetElementType;
  586. this.dataElementType = new.target.dataElementType;
  587. this.initialize();
  588. }
  589. initialize() {
  590. const meta = this._cachedMeta;
  591. this.configure();
  592. this.linkScales();
  593. meta._stacked = isStacked(meta.vScale, meta);
  594. this.addElements();
  595. if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
  596. 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");
  597. }
  598. }
  599. updateIndex(datasetIndex) {
  600. if (this.index !== datasetIndex) {
  601. clearStacks(this._cachedMeta);
  602. }
  603. this.index = datasetIndex;
  604. }
  605. linkScales() {
  606. const chart = this.chart;
  607. const meta = this._cachedMeta;
  608. const dataset = this.getDataset();
  609. const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
  610. const xid = meta.xAxisID = helpers_segment.valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
  611. const yid = meta.yAxisID = helpers_segment.valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
  612. const rid = meta.rAxisID = helpers_segment.valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
  613. const indexAxis = meta.indexAxis;
  614. const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
  615. const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
  616. meta.xScale = this.getScaleForId(xid);
  617. meta.yScale = this.getScaleForId(yid);
  618. meta.rScale = this.getScaleForId(rid);
  619. meta.iScale = this.getScaleForId(iid);
  620. meta.vScale = this.getScaleForId(vid);
  621. }
  622. getDataset() {
  623. return this.chart.data.datasets[this.index];
  624. }
  625. getMeta() {
  626. return this.chart.getDatasetMeta(this.index);
  627. }
  628. getScaleForId(scaleID) {
  629. return this.chart.scales[scaleID];
  630. }
  631. _getOtherScale(scale) {
  632. const meta = this._cachedMeta;
  633. return scale === meta.iScale ? meta.vScale : meta.iScale;
  634. }
  635. reset() {
  636. this._update('reset');
  637. }
  638. _destroy() {
  639. const meta = this._cachedMeta;
  640. if (this._data) {
  641. helpers_segment.unlistenArrayEvents(this._data, this);
  642. }
  643. if (meta._stacked) {
  644. clearStacks(meta);
  645. }
  646. }
  647. _dataCheck() {
  648. const dataset = this.getDataset();
  649. const data = dataset.data || (dataset.data = []);
  650. const _data = this._data;
  651. if (helpers_segment.isObject(data)) {
  652. this._data = convertObjectDataToArray(data);
  653. } else if (_data !== data) {
  654. if (_data) {
  655. helpers_segment.unlistenArrayEvents(_data, this);
  656. const meta = this._cachedMeta;
  657. clearStacks(meta);
  658. meta._parsed = [];
  659. }
  660. if (data && Object.isExtensible(data)) {
  661. helpers_segment.listenArrayEvents(data, this);
  662. }
  663. this._syncList = [];
  664. this._data = data;
  665. }
  666. }
  667. addElements() {
  668. const meta = this._cachedMeta;
  669. this._dataCheck();
  670. if (this.datasetElementType) {
  671. meta.dataset = new this.datasetElementType();
  672. }
  673. }
  674. buildOrUpdateElements(resetNewElements) {
  675. const meta = this._cachedMeta;
  676. const dataset = this.getDataset();
  677. let stackChanged = false;
  678. this._dataCheck();
  679. const oldStacked = meta._stacked;
  680. meta._stacked = isStacked(meta.vScale, meta);
  681. if (meta.stack !== dataset.stack) {
  682. stackChanged = true;
  683. clearStacks(meta);
  684. meta.stack = dataset.stack;
  685. }
  686. this._resyncElements(resetNewElements);
  687. if (stackChanged || oldStacked !== meta._stacked) {
  688. updateStacks(this, meta._parsed);
  689. }
  690. }
  691. configure() {
  692. const config = this.chart.config;
  693. const scopeKeys = config.datasetScopeKeys(this._type);
  694. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
  695. this.options = config.createResolver(scopes, this.getContext());
  696. this._parsing = this.options.parsing;
  697. this._cachedDataOpts = {};
  698. }
  699. parse(start, count) {
  700. const { _cachedMeta: meta , _data: data } = this;
  701. const { iScale , _stacked } = meta;
  702. const iAxis = iScale.axis;
  703. let sorted = start === 0 && count === data.length ? true : meta._sorted;
  704. let prev = start > 0 && meta._parsed[start - 1];
  705. let i, cur, parsed;
  706. if (this._parsing === false) {
  707. meta._parsed = data;
  708. meta._sorted = true;
  709. parsed = data;
  710. } else {
  711. if (helpers_segment.isArray(data[start])) {
  712. parsed = this.parseArrayData(meta, data, start, count);
  713. } else if (helpers_segment.isObject(data[start])) {
  714. parsed = this.parseObjectData(meta, data, start, count);
  715. } else {
  716. parsed = this.parsePrimitiveData(meta, data, start, count);
  717. }
  718. const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
  719. for(i = 0; i < count; ++i){
  720. meta._parsed[i + start] = cur = parsed[i];
  721. if (sorted) {
  722. if (isNotInOrderComparedToPrev()) {
  723. sorted = false;
  724. }
  725. prev = cur;
  726. }
  727. }
  728. meta._sorted = sorted;
  729. }
  730. if (_stacked) {
  731. updateStacks(this, parsed);
  732. }
  733. }
  734. parsePrimitiveData(meta, data, start, count) {
  735. const { iScale , vScale } = meta;
  736. const iAxis = iScale.axis;
  737. const vAxis = vScale.axis;
  738. const labels = iScale.getLabels();
  739. const singleScale = iScale === vScale;
  740. const parsed = new Array(count);
  741. let i, ilen, index;
  742. for(i = 0, ilen = count; i < ilen; ++i){
  743. index = i + start;
  744. parsed[i] = {
  745. [iAxis]: singleScale || iScale.parse(labels[index], index),
  746. [vAxis]: vScale.parse(data[index], index)
  747. };
  748. }
  749. return parsed;
  750. }
  751. parseArrayData(meta, data, start, count) {
  752. const { xScale , yScale } = meta;
  753. const parsed = new Array(count);
  754. let i, ilen, index, item;
  755. for(i = 0, ilen = count; i < ilen; ++i){
  756. index = i + start;
  757. item = data[index];
  758. parsed[i] = {
  759. x: xScale.parse(item[0], index),
  760. y: yScale.parse(item[1], index)
  761. };
  762. }
  763. return parsed;
  764. }
  765. parseObjectData(meta, data, start, count) {
  766. const { xScale , yScale } = meta;
  767. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  768. const parsed = new Array(count);
  769. let i, ilen, index, item;
  770. for(i = 0, ilen = count; i < ilen; ++i){
  771. index = i + start;
  772. item = data[index];
  773. parsed[i] = {
  774. x: xScale.parse(helpers_segment.resolveObjectKey(item, xAxisKey), index),
  775. y: yScale.parse(helpers_segment.resolveObjectKey(item, yAxisKey), index)
  776. };
  777. }
  778. return parsed;
  779. }
  780. getParsed(index) {
  781. return this._cachedMeta._parsed[index];
  782. }
  783. getDataElement(index) {
  784. return this._cachedMeta.data[index];
  785. }
  786. applyStack(scale, parsed, mode) {
  787. const chart = this.chart;
  788. const meta = this._cachedMeta;
  789. const value = parsed[scale.axis];
  790. const stack = {
  791. keys: getSortedDatasetIndices(chart, true),
  792. values: parsed._stacks[scale.axis]._visualValues
  793. };
  794. return applyStack(stack, value, meta.index, {
  795. mode
  796. });
  797. }
  798. updateRangeFromParsed(range, scale, parsed, stack) {
  799. const parsedValue = parsed[scale.axis];
  800. let value = parsedValue === null ? NaN : parsedValue;
  801. const values = stack && parsed._stacks[scale.axis];
  802. if (stack && values) {
  803. stack.values = values;
  804. value = applyStack(stack, parsedValue, this._cachedMeta.index);
  805. }
  806. range.min = Math.min(range.min, value);
  807. range.max = Math.max(range.max, value);
  808. }
  809. getMinMax(scale, canStack) {
  810. const meta = this._cachedMeta;
  811. const _parsed = meta._parsed;
  812. const sorted = meta._sorted && scale === meta.iScale;
  813. const ilen = _parsed.length;
  814. const otherScale = this._getOtherScale(scale);
  815. const stack = createStack(canStack, meta, this.chart);
  816. const range = {
  817. min: Number.POSITIVE_INFINITY,
  818. max: Number.NEGATIVE_INFINITY
  819. };
  820. const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
  821. let i, parsed;
  822. function _skip() {
  823. parsed = _parsed[i];
  824. const otherValue = parsed[otherScale.axis];
  825. return !helpers_segment.isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
  826. }
  827. for(i = 0; i < ilen; ++i){
  828. if (_skip()) {
  829. continue;
  830. }
  831. this.updateRangeFromParsed(range, scale, parsed, stack);
  832. if (sorted) {
  833. break;
  834. }
  835. }
  836. if (sorted) {
  837. for(i = ilen - 1; i >= 0; --i){
  838. if (_skip()) {
  839. continue;
  840. }
  841. this.updateRangeFromParsed(range, scale, parsed, stack);
  842. break;
  843. }
  844. }
  845. return range;
  846. }
  847. getAllParsedValues(scale) {
  848. const parsed = this._cachedMeta._parsed;
  849. const values = [];
  850. let i, ilen, value;
  851. for(i = 0, ilen = parsed.length; i < ilen; ++i){
  852. value = parsed[i][scale.axis];
  853. if (helpers_segment.isNumberFinite(value)) {
  854. values.push(value);
  855. }
  856. }
  857. return values;
  858. }
  859. getMaxOverflow() {
  860. return false;
  861. }
  862. getLabelAndValue(index) {
  863. const meta = this._cachedMeta;
  864. const iScale = meta.iScale;
  865. const vScale = meta.vScale;
  866. const parsed = this.getParsed(index);
  867. return {
  868. label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
  869. value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
  870. };
  871. }
  872. _update(mode) {
  873. const meta = this._cachedMeta;
  874. this.update(mode || 'default');
  875. meta._clip = toClip(helpers_segment.valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
  876. }
  877. update(mode) {}
  878. draw() {
  879. const ctx = this._ctx;
  880. const chart = this.chart;
  881. const meta = this._cachedMeta;
  882. const elements = meta.data || [];
  883. const area = chart.chartArea;
  884. const active = [];
  885. const start = this._drawStart || 0;
  886. const count = this._drawCount || elements.length - start;
  887. const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
  888. let i;
  889. if (meta.dataset) {
  890. meta.dataset.draw(ctx, area, start, count);
  891. }
  892. for(i = start; i < start + count; ++i){
  893. const element = elements[i];
  894. if (element.hidden) {
  895. continue;
  896. }
  897. if (element.active && drawActiveElementsOnTop) {
  898. active.push(element);
  899. } else {
  900. element.draw(ctx, area);
  901. }
  902. }
  903. for(i = 0; i < active.length; ++i){
  904. active[i].draw(ctx, area);
  905. }
  906. }
  907. getStyle(index, active) {
  908. const mode = active ? 'active' : 'default';
  909. return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
  910. }
  911. getContext(index, active, mode) {
  912. const dataset = this.getDataset();
  913. let context;
  914. if (index >= 0 && index < this._cachedMeta.data.length) {
  915. const element = this._cachedMeta.data[index];
  916. context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
  917. context.parsed = this.getParsed(index);
  918. context.raw = dataset.data[index];
  919. context.index = context.dataIndex = index;
  920. } else {
  921. context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
  922. context.dataset = dataset;
  923. context.index = context.datasetIndex = this.index;
  924. }
  925. context.active = !!active;
  926. context.mode = mode;
  927. return context;
  928. }
  929. resolveDatasetElementOptions(mode) {
  930. return this._resolveElementOptions(this.datasetElementType.id, mode);
  931. }
  932. resolveDataElementOptions(index, mode) {
  933. return this._resolveElementOptions(this.dataElementType.id, mode, index);
  934. }
  935. _resolveElementOptions(elementType, mode = 'default', index) {
  936. const active = mode === 'active';
  937. const cache = this._cachedDataOpts;
  938. const cacheKey = elementType + '-' + mode;
  939. const cached = cache[cacheKey];
  940. const sharing = this.enableOptionSharing && helpers_segment.defined(index);
  941. if (cached) {
  942. return cloneIfNotShared(cached, sharing);
  943. }
  944. const config = this.chart.config;
  945. const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
  946. const prefixes = active ? [
  947. `${elementType}Hover`,
  948. 'hover',
  949. elementType,
  950. ''
  951. ] : [
  952. elementType,
  953. ''
  954. ];
  955. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  956. const names = Object.keys(helpers_segment.defaults.elements[elementType]);
  957. const context = ()=>this.getContext(index, active, mode);
  958. const values = config.resolveNamedOptions(scopes, names, context, prefixes);
  959. if (values.$shared) {
  960. values.$shared = sharing;
  961. cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
  962. }
  963. return values;
  964. }
  965. _resolveAnimations(index, transition, active) {
  966. const chart = this.chart;
  967. const cache = this._cachedDataOpts;
  968. const cacheKey = `animation-${transition}`;
  969. const cached = cache[cacheKey];
  970. if (cached) {
  971. return cached;
  972. }
  973. let options;
  974. if (chart.options.animation !== false) {
  975. const config = this.chart.config;
  976. const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
  977. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  978. options = config.createResolver(scopes, this.getContext(index, active, transition));
  979. }
  980. const animations = new Animations(chart, options && options.animations);
  981. if (options && options._cacheable) {
  982. cache[cacheKey] = Object.freeze(animations);
  983. }
  984. return animations;
  985. }
  986. getSharedOptions(options) {
  987. if (!options.$shared) {
  988. return;
  989. }
  990. return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
  991. }
  992. includeOptions(mode, sharedOptions) {
  993. return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
  994. }
  995. _getSharedOptions(start, mode) {
  996. const firstOpts = this.resolveDataElementOptions(start, mode);
  997. const previouslySharedOptions = this._sharedOptions;
  998. const sharedOptions = this.getSharedOptions(firstOpts);
  999. const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
  1000. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  1001. return {
  1002. sharedOptions,
  1003. includeOptions
  1004. };
  1005. }
  1006. updateElement(element, index, properties, mode) {
  1007. if (isDirectUpdateMode(mode)) {
  1008. Object.assign(element, properties);
  1009. } else {
  1010. this._resolveAnimations(index, mode).update(element, properties);
  1011. }
  1012. }
  1013. updateSharedOptions(sharedOptions, mode, newOptions) {
  1014. if (sharedOptions && !isDirectUpdateMode(mode)) {
  1015. this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
  1016. }
  1017. }
  1018. _setStyle(element, index, mode, active) {
  1019. element.active = active;
  1020. const options = this.getStyle(index, active);
  1021. this._resolveAnimations(index, mode, active).update(element, {
  1022. options: !active && this.getSharedOptions(options) || options
  1023. });
  1024. }
  1025. removeHoverStyle(element, datasetIndex, index) {
  1026. this._setStyle(element, index, 'active', false);
  1027. }
  1028. setHoverStyle(element, datasetIndex, index) {
  1029. this._setStyle(element, index, 'active', true);
  1030. }
  1031. _removeDatasetHoverStyle() {
  1032. const element = this._cachedMeta.dataset;
  1033. if (element) {
  1034. this._setStyle(element, undefined, 'active', false);
  1035. }
  1036. }
  1037. _setDatasetHoverStyle() {
  1038. const element = this._cachedMeta.dataset;
  1039. if (element) {
  1040. this._setStyle(element, undefined, 'active', true);
  1041. }
  1042. }
  1043. _resyncElements(resetNewElements) {
  1044. const data = this._data;
  1045. const elements = this._cachedMeta.data;
  1046. for (const [method, arg1, arg2] of this._syncList){
  1047. this[method](arg1, arg2);
  1048. }
  1049. this._syncList = [];
  1050. const numMeta = elements.length;
  1051. const numData = data.length;
  1052. const count = Math.min(numData, numMeta);
  1053. if (count) {
  1054. this.parse(0, count);
  1055. }
  1056. if (numData > numMeta) {
  1057. this._insertElements(numMeta, numData - numMeta, resetNewElements);
  1058. } else if (numData < numMeta) {
  1059. this._removeElements(numData, numMeta - numData);
  1060. }
  1061. }
  1062. _insertElements(start, count, resetNewElements = true) {
  1063. const meta = this._cachedMeta;
  1064. const data = meta.data;
  1065. const end = start + count;
  1066. let i;
  1067. const move = (arr)=>{
  1068. arr.length += count;
  1069. for(i = arr.length - 1; i >= end; i--){
  1070. arr[i] = arr[i - count];
  1071. }
  1072. };
  1073. move(data);
  1074. for(i = start; i < end; ++i){
  1075. data[i] = new this.dataElementType();
  1076. }
  1077. if (this._parsing) {
  1078. move(meta._parsed);
  1079. }
  1080. this.parse(start, count);
  1081. if (resetNewElements) {
  1082. this.updateElements(data, start, count, 'reset');
  1083. }
  1084. }
  1085. updateElements(element, start, count, mode) {}
  1086. _removeElements(start, count) {
  1087. const meta = this._cachedMeta;
  1088. if (this._parsing) {
  1089. const removed = meta._parsed.splice(start, count);
  1090. if (meta._stacked) {
  1091. clearStacks(meta, removed);
  1092. }
  1093. }
  1094. meta.data.splice(start, count);
  1095. }
  1096. _sync(args) {
  1097. if (this._parsing) {
  1098. this._syncList.push(args);
  1099. } else {
  1100. const [method, arg1, arg2] = args;
  1101. this[method](arg1, arg2);
  1102. }
  1103. this.chart._dataChanges.push([
  1104. this.index,
  1105. ...args
  1106. ]);
  1107. }
  1108. _onDataPush() {
  1109. const count = arguments.length;
  1110. this._sync([
  1111. '_insertElements',
  1112. this.getDataset().data.length - count,
  1113. count
  1114. ]);
  1115. }
  1116. _onDataPop() {
  1117. this._sync([
  1118. '_removeElements',
  1119. this._cachedMeta.data.length - 1,
  1120. 1
  1121. ]);
  1122. }
  1123. _onDataShift() {
  1124. this._sync([
  1125. '_removeElements',
  1126. 0,
  1127. 1
  1128. ]);
  1129. }
  1130. _onDataSplice(start, count) {
  1131. if (count) {
  1132. this._sync([
  1133. '_removeElements',
  1134. start,
  1135. count
  1136. ]);
  1137. }
  1138. const newCount = arguments.length - 2;
  1139. if (newCount) {
  1140. this._sync([
  1141. '_insertElements',
  1142. start,
  1143. newCount
  1144. ]);
  1145. }
  1146. }
  1147. _onDataUnshift() {
  1148. this._sync([
  1149. '_insertElements',
  1150. 0,
  1151. arguments.length
  1152. ]);
  1153. }
  1154. }
  1155. function getAllScaleValues(scale, type) {
  1156. if (!scale._cache.$bar) {
  1157. const visibleMetas = scale.getMatchingVisibleMetas(type);
  1158. let values = [];
  1159. for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
  1160. values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
  1161. }
  1162. scale._cache.$bar = helpers_segment._arrayUnique(values.sort((a, b)=>a - b));
  1163. }
  1164. return scale._cache.$bar;
  1165. }
  1166. function computeMinSampleSize(meta) {
  1167. const scale = meta.iScale;
  1168. const values = getAllScaleValues(scale, meta.type);
  1169. let min = scale._length;
  1170. let i, ilen, curr, prev;
  1171. const updateMinAndPrev = ()=>{
  1172. if (curr === 32767 || curr === -32768) {
  1173. return;
  1174. }
  1175. if (helpers_segment.defined(prev)) {
  1176. min = Math.min(min, Math.abs(curr - prev) || min);
  1177. }
  1178. prev = curr;
  1179. };
  1180. for(i = 0, ilen = values.length; i < ilen; ++i){
  1181. curr = scale.getPixelForValue(values[i]);
  1182. updateMinAndPrev();
  1183. }
  1184. prev = undefined;
  1185. for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
  1186. curr = scale.getPixelForTick(i);
  1187. updateMinAndPrev();
  1188. }
  1189. return min;
  1190. }
  1191. function computeFitCategoryTraits(index, ruler, options, stackCount) {
  1192. const thickness = options.barThickness;
  1193. let size, ratio;
  1194. if (helpers_segment.isNullOrUndef(thickness)) {
  1195. size = ruler.min * options.categoryPercentage;
  1196. ratio = options.barPercentage;
  1197. } else {
  1198. size = thickness * stackCount;
  1199. ratio = 1;
  1200. }
  1201. return {
  1202. chunk: size / stackCount,
  1203. ratio,
  1204. start: ruler.pixels[index] - size / 2
  1205. };
  1206. }
  1207. function computeFlexCategoryTraits(index, ruler, options, stackCount) {
  1208. const pixels = ruler.pixels;
  1209. const curr = pixels[index];
  1210. let prev = index > 0 ? pixels[index - 1] : null;
  1211. let next = index < pixels.length - 1 ? pixels[index + 1] : null;
  1212. const percent = options.categoryPercentage;
  1213. if (prev === null) {
  1214. prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
  1215. }
  1216. if (next === null) {
  1217. next = curr + curr - prev;
  1218. }
  1219. const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
  1220. const size = Math.abs(next - prev) / 2 * percent;
  1221. return {
  1222. chunk: size / stackCount,
  1223. ratio: options.barPercentage,
  1224. start
  1225. };
  1226. }
  1227. function parseFloatBar(entry, item, vScale, i) {
  1228. const startValue = vScale.parse(entry[0], i);
  1229. const endValue = vScale.parse(entry[1], i);
  1230. const min = Math.min(startValue, endValue);
  1231. const max = Math.max(startValue, endValue);
  1232. let barStart = min;
  1233. let barEnd = max;
  1234. if (Math.abs(min) > Math.abs(max)) {
  1235. barStart = max;
  1236. barEnd = min;
  1237. }
  1238. item[vScale.axis] = barEnd;
  1239. item._custom = {
  1240. barStart,
  1241. barEnd,
  1242. start: startValue,
  1243. end: endValue,
  1244. min,
  1245. max
  1246. };
  1247. }
  1248. function parseValue(entry, item, vScale, i) {
  1249. if (helpers_segment.isArray(entry)) {
  1250. parseFloatBar(entry, item, vScale, i);
  1251. } else {
  1252. item[vScale.axis] = vScale.parse(entry, i);
  1253. }
  1254. return item;
  1255. }
  1256. function parseArrayOrPrimitive(meta, data, start, count) {
  1257. const iScale = meta.iScale;
  1258. const vScale = meta.vScale;
  1259. const labels = iScale.getLabels();
  1260. const singleScale = iScale === vScale;
  1261. const parsed = [];
  1262. let i, ilen, item, entry;
  1263. for(i = start, ilen = start + count; i < ilen; ++i){
  1264. entry = data[i];
  1265. item = {};
  1266. item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
  1267. parsed.push(parseValue(entry, item, vScale, i));
  1268. }
  1269. return parsed;
  1270. }
  1271. function isFloatBar(custom) {
  1272. return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  1273. }
  1274. function barSign(size, vScale, actualBase) {
  1275. if (size !== 0) {
  1276. return helpers_segment.sign(size);
  1277. }
  1278. return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  1279. }
  1280. function borderProps(properties) {
  1281. let reverse, start, end, top, bottom;
  1282. if (properties.horizontal) {
  1283. reverse = properties.base > properties.x;
  1284. start = 'left';
  1285. end = 'right';
  1286. } else {
  1287. reverse = properties.base < properties.y;
  1288. start = 'bottom';
  1289. end = 'top';
  1290. }
  1291. if (reverse) {
  1292. top = 'end';
  1293. bottom = 'start';
  1294. } else {
  1295. top = 'start';
  1296. bottom = 'end';
  1297. }
  1298. return {
  1299. start,
  1300. end,
  1301. reverse,
  1302. top,
  1303. bottom
  1304. };
  1305. }
  1306. function setBorderSkipped(properties, options, stack, index) {
  1307. let edge = options.borderSkipped;
  1308. const res = {};
  1309. if (!edge) {
  1310. properties.borderSkipped = res;
  1311. return;
  1312. }
  1313. if (edge === true) {
  1314. properties.borderSkipped = {
  1315. top: true,
  1316. right: true,
  1317. bottom: true,
  1318. left: true
  1319. };
  1320. return;
  1321. }
  1322. const { start , end , reverse , top , bottom } = borderProps(properties);
  1323. if (edge === 'middle' && stack) {
  1324. properties.enableBorderRadius = true;
  1325. if ((stack._top || 0) === index) {
  1326. edge = top;
  1327. } else if ((stack._bottom || 0) === index) {
  1328. edge = bottom;
  1329. } else {
  1330. res[parseEdge(bottom, start, end, reverse)] = true;
  1331. edge = top;
  1332. }
  1333. }
  1334. res[parseEdge(edge, start, end, reverse)] = true;
  1335. properties.borderSkipped = res;
  1336. }
  1337. function parseEdge(edge, a, b, reverse) {
  1338. if (reverse) {
  1339. edge = swap(edge, a, b);
  1340. edge = startEnd(edge, b, a);
  1341. } else {
  1342. edge = startEnd(edge, a, b);
  1343. }
  1344. return edge;
  1345. }
  1346. function swap(orig, v1, v2) {
  1347. return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  1348. }
  1349. function startEnd(v, start, end) {
  1350. return v === 'start' ? start : v === 'end' ? end : v;
  1351. }
  1352. function setInflateAmount(properties, { inflateAmount }, ratio) {
  1353. properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
  1354. }
  1355. class BarController extends DatasetController {
  1356. static id = 'bar';
  1357. static defaults = {
  1358. datasetElementType: false,
  1359. dataElementType: 'bar',
  1360. categoryPercentage: 0.8,
  1361. barPercentage: 0.9,
  1362. grouped: true,
  1363. animations: {
  1364. numbers: {
  1365. type: 'number',
  1366. properties: [
  1367. 'x',
  1368. 'y',
  1369. 'base',
  1370. 'width',
  1371. 'height'
  1372. ]
  1373. }
  1374. }
  1375. };
  1376. static overrides = {
  1377. scales: {
  1378. _index_: {
  1379. type: 'category',
  1380. offset: true,
  1381. grid: {
  1382. offset: true
  1383. }
  1384. },
  1385. _value_: {
  1386. type: 'linear',
  1387. beginAtZero: true
  1388. }
  1389. }
  1390. };
  1391. parsePrimitiveData(meta, data, start, count) {
  1392. return parseArrayOrPrimitive(meta, data, start, count);
  1393. }
  1394. parseArrayData(meta, data, start, count) {
  1395. return parseArrayOrPrimitive(meta, data, start, count);
  1396. }
  1397. parseObjectData(meta, data, start, count) {
  1398. const { iScale , vScale } = meta;
  1399. const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
  1400. const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
  1401. const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
  1402. const parsed = [];
  1403. let i, ilen, item, obj;
  1404. for(i = start, ilen = start + count; i < ilen; ++i){
  1405. obj = data[i];
  1406. item = {};
  1407. item[iScale.axis] = iScale.parse(helpers_segment.resolveObjectKey(obj, iAxisKey), i);
  1408. parsed.push(parseValue(helpers_segment.resolveObjectKey(obj, vAxisKey), item, vScale, i));
  1409. }
  1410. return parsed;
  1411. }
  1412. updateRangeFromParsed(range, scale, parsed, stack) {
  1413. super.updateRangeFromParsed(range, scale, parsed, stack);
  1414. const custom = parsed._custom;
  1415. if (custom && scale === this._cachedMeta.vScale) {
  1416. range.min = Math.min(range.min, custom.min);
  1417. range.max = Math.max(range.max, custom.max);
  1418. }
  1419. }
  1420. getMaxOverflow() {
  1421. return 0;
  1422. }
  1423. getLabelAndValue(index) {
  1424. const meta = this._cachedMeta;
  1425. const { iScale , vScale } = meta;
  1426. const parsed = this.getParsed(index);
  1427. const custom = parsed._custom;
  1428. const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
  1429. return {
  1430. label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
  1431. value
  1432. };
  1433. }
  1434. initialize() {
  1435. this.enableOptionSharing = true;
  1436. super.initialize();
  1437. const meta = this._cachedMeta;
  1438. meta.stack = this.getDataset().stack;
  1439. }
  1440. update(mode) {
  1441. const meta = this._cachedMeta;
  1442. this.updateElements(meta.data, 0, meta.data.length, mode);
  1443. }
  1444. updateElements(bars, start, count, mode) {
  1445. const reset = mode === 'reset';
  1446. const { index , _cachedMeta: { vScale } } = this;
  1447. const base = vScale.getBasePixel();
  1448. const horizontal = vScale.isHorizontal();
  1449. const ruler = this._getRuler();
  1450. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1451. for(let i = start; i < start + count; i++){
  1452. const parsed = this.getParsed(i);
  1453. const vpixels = reset || helpers_segment.isNullOrUndef(parsed[vScale.axis]) ? {
  1454. base,
  1455. head: base
  1456. } : this._calculateBarValuePixels(i);
  1457. const ipixels = this._calculateBarIndexPixels(i, ruler);
  1458. const stack = (parsed._stacks || {})[vScale.axis];
  1459. const properties = {
  1460. horizontal,
  1461. base: vpixels.base,
  1462. enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
  1463. x: horizontal ? vpixels.head : ipixels.center,
  1464. y: horizontal ? ipixels.center : vpixels.head,
  1465. height: horizontal ? ipixels.size : Math.abs(vpixels.size),
  1466. width: horizontal ? Math.abs(vpixels.size) : ipixels.size
  1467. };
  1468. if (includeOptions) {
  1469. properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
  1470. }
  1471. const options = properties.options || bars[i].options;
  1472. setBorderSkipped(properties, options, stack, index);
  1473. setInflateAmount(properties, options, ruler.ratio);
  1474. this.updateElement(bars[i], i, properties, mode);
  1475. }
  1476. }
  1477. _getStacks(last, dataIndex) {
  1478. const { iScale } = this._cachedMeta;
  1479. const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
  1480. const stacked = iScale.options.stacked;
  1481. const stacks = [];
  1482. const skipNull = (meta)=>{
  1483. const parsed = meta.controller.getParsed(dataIndex);
  1484. const val = parsed && parsed[meta.vScale.axis];
  1485. if (helpers_segment.isNullOrUndef(val) || isNaN(val)) {
  1486. return true;
  1487. }
  1488. };
  1489. for (const meta of metasets){
  1490. if (dataIndex !== undefined && skipNull(meta)) {
  1491. continue;
  1492. }
  1493. if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
  1494. stacks.push(meta.stack);
  1495. }
  1496. if (meta.index === last) {
  1497. break;
  1498. }
  1499. }
  1500. if (!stacks.length) {
  1501. stacks.push(undefined);
  1502. }
  1503. return stacks;
  1504. }
  1505. _getStackCount(index) {
  1506. return this._getStacks(undefined, index).length;
  1507. }
  1508. _getStackIndex(datasetIndex, name, dataIndex) {
  1509. const stacks = this._getStacks(datasetIndex, dataIndex);
  1510. const index = name !== undefined ? stacks.indexOf(name) : -1;
  1511. return index === -1 ? stacks.length - 1 : index;
  1512. }
  1513. _getRuler() {
  1514. const opts = this.options;
  1515. const meta = this._cachedMeta;
  1516. const iScale = meta.iScale;
  1517. const pixels = [];
  1518. let i, ilen;
  1519. for(i = 0, ilen = meta.data.length; i < ilen; ++i){
  1520. pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
  1521. }
  1522. const barThickness = opts.barThickness;
  1523. const min = barThickness || computeMinSampleSize(meta);
  1524. return {
  1525. min,
  1526. pixels,
  1527. start: iScale._startPixel,
  1528. end: iScale._endPixel,
  1529. stackCount: this._getStackCount(),
  1530. scale: iScale,
  1531. grouped: opts.grouped,
  1532. ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
  1533. };
  1534. }
  1535. _calculateBarValuePixels(index) {
  1536. const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
  1537. const actualBase = baseValue || 0;
  1538. const parsed = this.getParsed(index);
  1539. const custom = parsed._custom;
  1540. const floating = isFloatBar(custom);
  1541. let value = parsed[vScale.axis];
  1542. let start = 0;
  1543. let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
  1544. let head, size;
  1545. if (length !== value) {
  1546. start = length - value;
  1547. length = value;
  1548. }
  1549. if (floating) {
  1550. value = custom.barStart;
  1551. length = custom.barEnd - custom.barStart;
  1552. if (value !== 0 && helpers_segment.sign(value) !== helpers_segment.sign(custom.barEnd)) {
  1553. start = 0;
  1554. }
  1555. start += value;
  1556. }
  1557. const startValue = !helpers_segment.isNullOrUndef(baseValue) && !floating ? baseValue : start;
  1558. let base = vScale.getPixelForValue(startValue);
  1559. if (this.chart.getDataVisibility(index)) {
  1560. head = vScale.getPixelForValue(start + length);
  1561. } else {
  1562. head = base;
  1563. }
  1564. size = head - base;
  1565. if (Math.abs(size) < minBarLength) {
  1566. size = barSign(size, vScale, actualBase) * minBarLength;
  1567. if (value === actualBase) {
  1568. base -= size / 2;
  1569. }
  1570. const startPixel = vScale.getPixelForDecimal(0);
  1571. const endPixel = vScale.getPixelForDecimal(1);
  1572. const min = Math.min(startPixel, endPixel);
  1573. const max = Math.max(startPixel, endPixel);
  1574. base = Math.max(Math.min(base, max), min);
  1575. head = base + size;
  1576. if (_stacked && !floating) {
  1577. parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
  1578. }
  1579. }
  1580. if (base === vScale.getPixelForValue(actualBase)) {
  1581. const halfGrid = helpers_segment.sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
  1582. base += halfGrid;
  1583. size -= halfGrid;
  1584. }
  1585. return {
  1586. size,
  1587. base,
  1588. head,
  1589. center: head + size / 2
  1590. };
  1591. }
  1592. _calculateBarIndexPixels(index, ruler) {
  1593. const scale = ruler.scale;
  1594. const options = this.options;
  1595. const skipNull = options.skipNull;
  1596. const maxBarThickness = helpers_segment.valueOrDefault(options.maxBarThickness, Infinity);
  1597. let center, size;
  1598. if (ruler.grouped) {
  1599. const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
  1600. const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
  1601. const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
  1602. center = range.start + range.chunk * stackIndex + range.chunk / 2;
  1603. size = Math.min(maxBarThickness, range.chunk * range.ratio);
  1604. } else {
  1605. center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
  1606. size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
  1607. }
  1608. return {
  1609. base: center - size / 2,
  1610. head: center + size / 2,
  1611. center,
  1612. size
  1613. };
  1614. }
  1615. draw() {
  1616. const meta = this._cachedMeta;
  1617. const vScale = meta.vScale;
  1618. const rects = meta.data;
  1619. const ilen = rects.length;
  1620. let i = 0;
  1621. for(; i < ilen; ++i){
  1622. if (this.getParsed(i)[vScale.axis] !== null) {
  1623. rects[i].draw(this._ctx);
  1624. }
  1625. }
  1626. }
  1627. }
  1628. class BubbleController extends DatasetController {
  1629. static id = 'bubble';
  1630. static defaults = {
  1631. datasetElementType: false,
  1632. dataElementType: 'point',
  1633. animations: {
  1634. numbers: {
  1635. type: 'number',
  1636. properties: [
  1637. 'x',
  1638. 'y',
  1639. 'borderWidth',
  1640. 'radius'
  1641. ]
  1642. }
  1643. }
  1644. };
  1645. static overrides = {
  1646. scales: {
  1647. x: {
  1648. type: 'linear'
  1649. },
  1650. y: {
  1651. type: 'linear'
  1652. }
  1653. }
  1654. };
  1655. initialize() {
  1656. this.enableOptionSharing = true;
  1657. super.initialize();
  1658. }
  1659. parsePrimitiveData(meta, data, start, count) {
  1660. const parsed = super.parsePrimitiveData(meta, data, start, count);
  1661. for(let i = 0; i < parsed.length; i++){
  1662. parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
  1663. }
  1664. return parsed;
  1665. }
  1666. parseArrayData(meta, data, start, count) {
  1667. const parsed = super.parseArrayData(meta, data, start, count);
  1668. for(let i = 0; i < parsed.length; i++){
  1669. const item = data[start + i];
  1670. parsed[i]._custom = helpers_segment.valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
  1671. }
  1672. return parsed;
  1673. }
  1674. parseObjectData(meta, data, start, count) {
  1675. const parsed = super.parseObjectData(meta, data, start, count);
  1676. for(let i = 0; i < parsed.length; i++){
  1677. const item = data[start + i];
  1678. parsed[i]._custom = helpers_segment.valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
  1679. }
  1680. return parsed;
  1681. }
  1682. getMaxOverflow() {
  1683. const data = this._cachedMeta.data;
  1684. let max = 0;
  1685. for(let i = data.length - 1; i >= 0; --i){
  1686. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  1687. }
  1688. return max > 0 && max;
  1689. }
  1690. getLabelAndValue(index) {
  1691. const meta = this._cachedMeta;
  1692. const labels = this.chart.data.labels || [];
  1693. const { xScale , yScale } = meta;
  1694. const parsed = this.getParsed(index);
  1695. const x = xScale.getLabelForValue(parsed.x);
  1696. const y = yScale.getLabelForValue(parsed.y);
  1697. const r = parsed._custom;
  1698. return {
  1699. label: labels[index] || '',
  1700. value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
  1701. };
  1702. }
  1703. update(mode) {
  1704. const points = this._cachedMeta.data;
  1705. this.updateElements(points, 0, points.length, mode);
  1706. }
  1707. updateElements(points, start, count, mode) {
  1708. const reset = mode === 'reset';
  1709. const { iScale , vScale } = this._cachedMeta;
  1710. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1711. const iAxis = iScale.axis;
  1712. const vAxis = vScale.axis;
  1713. for(let i = start; i < start + count; i++){
  1714. const point = points[i];
  1715. const parsed = !reset && this.getParsed(i);
  1716. const properties = {};
  1717. const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
  1718. const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
  1719. properties.skip = isNaN(iPixel) || isNaN(vPixel);
  1720. if (includeOptions) {
  1721. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  1722. if (reset) {
  1723. properties.options.radius = 0;
  1724. }
  1725. }
  1726. this.updateElement(point, i, properties, mode);
  1727. }
  1728. }
  1729. resolveDataElementOptions(index, mode) {
  1730. const parsed = this.getParsed(index);
  1731. let values = super.resolveDataElementOptions(index, mode);
  1732. if (values.$shared) {
  1733. values = Object.assign({}, values, {
  1734. $shared: false
  1735. });
  1736. }
  1737. const radius = values.radius;
  1738. if (mode !== 'active') {
  1739. values.radius = 0;
  1740. }
  1741. values.radius += helpers_segment.valueOrDefault(parsed && parsed._custom, radius);
  1742. return values;
  1743. }
  1744. }
  1745. function getRatioAndOffset(rotation, circumference, cutout) {
  1746. let ratioX = 1;
  1747. let ratioY = 1;
  1748. let offsetX = 0;
  1749. let offsetY = 0;
  1750. if (circumference < helpers_segment.TAU) {
  1751. const startAngle = rotation;
  1752. const endAngle = startAngle + circumference;
  1753. const startX = Math.cos(startAngle);
  1754. const startY = Math.sin(startAngle);
  1755. const endX = Math.cos(endAngle);
  1756. const endY = Math.sin(endAngle);
  1757. const calcMax = (angle, a, b)=>helpers_segment._angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
  1758. const calcMin = (angle, a, b)=>helpers_segment._angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
  1759. const maxX = calcMax(0, startX, endX);
  1760. const maxY = calcMax(helpers_segment.HALF_PI, startY, endY);
  1761. const minX = calcMin(helpers_segment.PI, startX, endX);
  1762. const minY = calcMin(helpers_segment.PI + helpers_segment.HALF_PI, startY, endY);
  1763. ratioX = (maxX - minX) / 2;
  1764. ratioY = (maxY - minY) / 2;
  1765. offsetX = -(maxX + minX) / 2;
  1766. offsetY = -(maxY + minY) / 2;
  1767. }
  1768. return {
  1769. ratioX,
  1770. ratioY,
  1771. offsetX,
  1772. offsetY
  1773. };
  1774. }
  1775. class DoughnutController extends DatasetController {
  1776. static id = 'doughnut';
  1777. static defaults = {
  1778. datasetElementType: false,
  1779. dataElementType: 'arc',
  1780. animation: {
  1781. animateRotate: true,
  1782. animateScale: false
  1783. },
  1784. animations: {
  1785. numbers: {
  1786. type: 'number',
  1787. properties: [
  1788. 'circumference',
  1789. 'endAngle',
  1790. 'innerRadius',
  1791. 'outerRadius',
  1792. 'startAngle',
  1793. 'x',
  1794. 'y',
  1795. 'offset',
  1796. 'borderWidth',
  1797. 'spacing'
  1798. ]
  1799. }
  1800. },
  1801. cutout: '50%',
  1802. rotation: 0,
  1803. circumference: 360,
  1804. radius: '100%',
  1805. spacing: 0,
  1806. indexAxis: 'r'
  1807. };
  1808. static descriptors = {
  1809. _scriptable: (name)=>name !== 'spacing',
  1810. _indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash')
  1811. };
  1812. static overrides = {
  1813. aspectRatio: 1,
  1814. plugins: {
  1815. legend: {
  1816. labels: {
  1817. generateLabels (chart) {
  1818. const data = chart.data;
  1819. if (data.labels.length && data.datasets.length) {
  1820. const { labels: { pointStyle , color } } = chart.legend.options;
  1821. return data.labels.map((label, i)=>{
  1822. const meta = chart.getDatasetMeta(0);
  1823. const style = meta.controller.getStyle(i);
  1824. return {
  1825. text: label,
  1826. fillStyle: style.backgroundColor,
  1827. strokeStyle: style.borderColor,
  1828. fontColor: color,
  1829. lineWidth: style.borderWidth,
  1830. pointStyle: pointStyle,
  1831. hidden: !chart.getDataVisibility(i),
  1832. index: i
  1833. };
  1834. });
  1835. }
  1836. return [];
  1837. }
  1838. },
  1839. onClick (e, legendItem, legend) {
  1840. legend.chart.toggleDataVisibility(legendItem.index);
  1841. legend.chart.update();
  1842. }
  1843. }
  1844. }
  1845. };
  1846. constructor(chart, datasetIndex){
  1847. super(chart, datasetIndex);
  1848. this.enableOptionSharing = true;
  1849. this.innerRadius = undefined;
  1850. this.outerRadius = undefined;
  1851. this.offsetX = undefined;
  1852. this.offsetY = undefined;
  1853. }
  1854. linkScales() {}
  1855. parse(start, count) {
  1856. const data = this.getDataset().data;
  1857. const meta = this._cachedMeta;
  1858. if (this._parsing === false) {
  1859. meta._parsed = data;
  1860. } else {
  1861. let getter = (i)=>+data[i];
  1862. if (helpers_segment.isObject(data[start])) {
  1863. const { key ='value' } = this._parsing;
  1864. getter = (i)=>+helpers_segment.resolveObjectKey(data[i], key);
  1865. }
  1866. let i, ilen;
  1867. for(i = start, ilen = start + count; i < ilen; ++i){
  1868. meta._parsed[i] = getter(i);
  1869. }
  1870. }
  1871. }
  1872. _getRotation() {
  1873. return helpers_segment.toRadians(this.options.rotation - 90);
  1874. }
  1875. _getCircumference() {
  1876. return helpers_segment.toRadians(this.options.circumference);
  1877. }
  1878. _getRotationExtents() {
  1879. let min = helpers_segment.TAU;
  1880. let max = -helpers_segment.TAU;
  1881. for(let i = 0; i < this.chart.data.datasets.length; ++i){
  1882. if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
  1883. const controller = this.chart.getDatasetMeta(i).controller;
  1884. const rotation = controller._getRotation();
  1885. const circumference = controller._getCircumference();
  1886. min = Math.min(min, rotation);
  1887. max = Math.max(max, rotation + circumference);
  1888. }
  1889. }
  1890. return {
  1891. rotation: min,
  1892. circumference: max - min
  1893. };
  1894. }
  1895. update(mode) {
  1896. const chart = this.chart;
  1897. const { chartArea } = chart;
  1898. const meta = this._cachedMeta;
  1899. const arcs = meta.data;
  1900. const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
  1901. const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
  1902. const cutout = Math.min(helpers_segment.toPercentage(this.options.cutout, maxSize), 1);
  1903. const chartWeight = this._getRingWeight(this.index);
  1904. const { circumference , rotation } = this._getRotationExtents();
  1905. const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
  1906. const maxWidth = (chartArea.width - spacing) / ratioX;
  1907. const maxHeight = (chartArea.height - spacing) / ratioY;
  1908. const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
  1909. const outerRadius = helpers_segment.toDimension(this.options.radius, maxRadius);
  1910. const innerRadius = Math.max(outerRadius * cutout, 0);
  1911. const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
  1912. this.offsetX = offsetX * outerRadius;
  1913. this.offsetY = offsetY * outerRadius;
  1914. meta.total = this.calculateTotal();
  1915. this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
  1916. this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
  1917. this.updateElements(arcs, 0, arcs.length, mode);
  1918. }
  1919. _circumference(i, reset) {
  1920. const opts = this.options;
  1921. const meta = this._cachedMeta;
  1922. const circumference = this._getCircumference();
  1923. if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
  1924. return 0;
  1925. }
  1926. return this.calculateCircumference(meta._parsed[i] * circumference / helpers_segment.TAU);
  1927. }
  1928. updateElements(arcs, start, count, mode) {
  1929. const reset = mode === 'reset';
  1930. const chart = this.chart;
  1931. const chartArea = chart.chartArea;
  1932. const opts = chart.options;
  1933. const animationOpts = opts.animation;
  1934. const centerX = (chartArea.left + chartArea.right) / 2;
  1935. const centerY = (chartArea.top + chartArea.bottom) / 2;
  1936. const animateScale = reset && animationOpts.animateScale;
  1937. const innerRadius = animateScale ? 0 : this.innerRadius;
  1938. const outerRadius = animateScale ? 0 : this.outerRadius;
  1939. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  1940. let startAngle = this._getRotation();
  1941. let i;
  1942. for(i = 0; i < start; ++i){
  1943. startAngle += this._circumference(i, reset);
  1944. }
  1945. for(i = start; i < start + count; ++i){
  1946. const circumference = this._circumference(i, reset);
  1947. const arc = arcs[i];
  1948. const properties = {
  1949. x: centerX + this.offsetX,
  1950. y: centerY + this.offsetY,
  1951. startAngle,
  1952. endAngle: startAngle + circumference,
  1953. circumference,
  1954. outerRadius,
  1955. innerRadius
  1956. };
  1957. if (includeOptions) {
  1958. properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
  1959. }
  1960. startAngle += circumference;
  1961. this.updateElement(arc, i, properties, mode);
  1962. }
  1963. }
  1964. calculateTotal() {
  1965. const meta = this._cachedMeta;
  1966. const metaData = meta.data;
  1967. let total = 0;
  1968. let i;
  1969. for(i = 0; i < metaData.length; i++){
  1970. const value = meta._parsed[i];
  1971. if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
  1972. total += Math.abs(value);
  1973. }
  1974. }
  1975. return total;
  1976. }
  1977. calculateCircumference(value) {
  1978. const total = this._cachedMeta.total;
  1979. if (total > 0 && !isNaN(value)) {
  1980. return helpers_segment.TAU * (Math.abs(value) / total);
  1981. }
  1982. return 0;
  1983. }
  1984. getLabelAndValue(index) {
  1985. const meta = this._cachedMeta;
  1986. const chart = this.chart;
  1987. const labels = chart.data.labels || [];
  1988. const value = helpers_segment.formatNumber(meta._parsed[index], chart.options.locale);
  1989. return {
  1990. label: labels[index] || '',
  1991. value
  1992. };
  1993. }
  1994. getMaxBorderWidth(arcs) {
  1995. let max = 0;
  1996. const chart = this.chart;
  1997. let i, ilen, meta, controller, options;
  1998. if (!arcs) {
  1999. for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
  2000. if (chart.isDatasetVisible(i)) {
  2001. meta = chart.getDatasetMeta(i);
  2002. arcs = meta.data;
  2003. controller = meta.controller;
  2004. break;
  2005. }
  2006. }
  2007. }
  2008. if (!arcs) {
  2009. return 0;
  2010. }
  2011. for(i = 0, ilen = arcs.length; i < ilen; ++i){
  2012. options = controller.resolveDataElementOptions(i);
  2013. if (options.borderAlign !== 'inner') {
  2014. max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
  2015. }
  2016. }
  2017. return max;
  2018. }
  2019. getMaxOffset(arcs) {
  2020. let max = 0;
  2021. for(let i = 0, ilen = arcs.length; i < ilen; ++i){
  2022. const options = this.resolveDataElementOptions(i);
  2023. max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
  2024. }
  2025. return max;
  2026. }
  2027. _getRingWeightOffset(datasetIndex) {
  2028. let ringWeightOffset = 0;
  2029. for(let i = 0; i < datasetIndex; ++i){
  2030. if (this.chart.isDatasetVisible(i)) {
  2031. ringWeightOffset += this._getRingWeight(i);
  2032. }
  2033. }
  2034. return ringWeightOffset;
  2035. }
  2036. _getRingWeight(datasetIndex) {
  2037. return Math.max(helpers_segment.valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
  2038. }
  2039. _getVisibleDatasetWeightTotal() {
  2040. return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
  2041. }
  2042. }
  2043. class LineController extends DatasetController {
  2044. static id = 'line';
  2045. static defaults = {
  2046. datasetElementType: 'line',
  2047. dataElementType: 'point',
  2048. showLine: true,
  2049. spanGaps: false
  2050. };
  2051. static overrides = {
  2052. scales: {
  2053. _index_: {
  2054. type: 'category'
  2055. },
  2056. _value_: {
  2057. type: 'linear'
  2058. }
  2059. }
  2060. };
  2061. initialize() {
  2062. this.enableOptionSharing = true;
  2063. this.supportsDecimation = true;
  2064. super.initialize();
  2065. }
  2066. update(mode) {
  2067. const meta = this._cachedMeta;
  2068. const { dataset: line , data: points = [] , _dataset } = meta;
  2069. const animationsDisabled = this.chart._animationsDisabled;
  2070. let { start , count } = helpers_segment._getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2071. this._drawStart = start;
  2072. this._drawCount = count;
  2073. if (helpers_segment._scaleRangesChanged(meta)) {
  2074. start = 0;
  2075. count = points.length;
  2076. }
  2077. line._chart = this.chart;
  2078. line._datasetIndex = this.index;
  2079. line._decimated = !!_dataset._decimated;
  2080. line.points = points;
  2081. const options = this.resolveDatasetElementOptions(mode);
  2082. if (!this.options.showLine) {
  2083. options.borderWidth = 0;
  2084. }
  2085. options.segment = this.options.segment;
  2086. this.updateElement(line, undefined, {
  2087. animated: !animationsDisabled,
  2088. options
  2089. }, mode);
  2090. this.updateElements(points, start, count, mode);
  2091. }
  2092. updateElements(points, start, count, mode) {
  2093. const reset = mode === 'reset';
  2094. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2095. const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
  2096. const iAxis = iScale.axis;
  2097. const vAxis = vScale.axis;
  2098. const { spanGaps , segment } = this.options;
  2099. const maxGapLength = helpers_segment.isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2100. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2101. const end = start + count;
  2102. const pointsCount = points.length;
  2103. let prevParsed = start > 0 && this.getParsed(start - 1);
  2104. for(let i = 0; i < pointsCount; ++i){
  2105. const point = points[i];
  2106. const properties = directUpdate ? point : {};
  2107. if (i < start || i >= end) {
  2108. properties.skip = true;
  2109. continue;
  2110. }
  2111. const parsed = this.getParsed(i);
  2112. const nullData = helpers_segment.isNullOrUndef(parsed[vAxis]);
  2113. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2114. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2115. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2116. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2117. if (segment) {
  2118. properties.parsed = parsed;
  2119. properties.raw = _dataset.data[i];
  2120. }
  2121. if (includeOptions) {
  2122. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2123. }
  2124. if (!directUpdate) {
  2125. this.updateElement(point, i, properties, mode);
  2126. }
  2127. prevParsed = parsed;
  2128. }
  2129. }
  2130. getMaxOverflow() {
  2131. const meta = this._cachedMeta;
  2132. const dataset = meta.dataset;
  2133. const border = dataset.options && dataset.options.borderWidth || 0;
  2134. const data = meta.data || [];
  2135. if (!data.length) {
  2136. return border;
  2137. }
  2138. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2139. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2140. return Math.max(border, firstPoint, lastPoint) / 2;
  2141. }
  2142. draw() {
  2143. const meta = this._cachedMeta;
  2144. meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
  2145. super.draw();
  2146. }
  2147. }
  2148. class PolarAreaController extends DatasetController {
  2149. static id = 'polarArea';
  2150. static defaults = {
  2151. dataElementType: 'arc',
  2152. animation: {
  2153. animateRotate: true,
  2154. animateScale: true
  2155. },
  2156. animations: {
  2157. numbers: {
  2158. type: 'number',
  2159. properties: [
  2160. 'x',
  2161. 'y',
  2162. 'startAngle',
  2163. 'endAngle',
  2164. 'innerRadius',
  2165. 'outerRadius'
  2166. ]
  2167. }
  2168. },
  2169. indexAxis: 'r',
  2170. startAngle: 0
  2171. };
  2172. static overrides = {
  2173. aspectRatio: 1,
  2174. plugins: {
  2175. legend: {
  2176. labels: {
  2177. generateLabels (chart) {
  2178. const data = chart.data;
  2179. if (data.labels.length && data.datasets.length) {
  2180. const { labels: { pointStyle , color } } = chart.legend.options;
  2181. return data.labels.map((label, i)=>{
  2182. const meta = chart.getDatasetMeta(0);
  2183. const style = meta.controller.getStyle(i);
  2184. return {
  2185. text: label,
  2186. fillStyle: style.backgroundColor,
  2187. strokeStyle: style.borderColor,
  2188. fontColor: color,
  2189. lineWidth: style.borderWidth,
  2190. pointStyle: pointStyle,
  2191. hidden: !chart.getDataVisibility(i),
  2192. index: i
  2193. };
  2194. });
  2195. }
  2196. return [];
  2197. }
  2198. },
  2199. onClick (e, legendItem, legend) {
  2200. legend.chart.toggleDataVisibility(legendItem.index);
  2201. legend.chart.update();
  2202. }
  2203. }
  2204. },
  2205. scales: {
  2206. r: {
  2207. type: 'radialLinear',
  2208. angleLines: {
  2209. display: false
  2210. },
  2211. beginAtZero: true,
  2212. grid: {
  2213. circular: true
  2214. },
  2215. pointLabels: {
  2216. display: false
  2217. },
  2218. startAngle: 0
  2219. }
  2220. }
  2221. };
  2222. constructor(chart, datasetIndex){
  2223. super(chart, datasetIndex);
  2224. this.innerRadius = undefined;
  2225. this.outerRadius = undefined;
  2226. }
  2227. getLabelAndValue(index) {
  2228. const meta = this._cachedMeta;
  2229. const chart = this.chart;
  2230. const labels = chart.data.labels || [];
  2231. const value = helpers_segment.formatNumber(meta._parsed[index].r, chart.options.locale);
  2232. return {
  2233. label: labels[index] || '',
  2234. value
  2235. };
  2236. }
  2237. parseObjectData(meta, data, start, count) {
  2238. return helpers_segment._parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2239. }
  2240. update(mode) {
  2241. const arcs = this._cachedMeta.data;
  2242. this._updateRadius();
  2243. this.updateElements(arcs, 0, arcs.length, mode);
  2244. }
  2245. getMinMax() {
  2246. const meta = this._cachedMeta;
  2247. const range = {
  2248. min: Number.POSITIVE_INFINITY,
  2249. max: Number.NEGATIVE_INFINITY
  2250. };
  2251. meta.data.forEach((element, index)=>{
  2252. const parsed = this.getParsed(index).r;
  2253. if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
  2254. if (parsed < range.min) {
  2255. range.min = parsed;
  2256. }
  2257. if (parsed > range.max) {
  2258. range.max = parsed;
  2259. }
  2260. }
  2261. });
  2262. return range;
  2263. }
  2264. _updateRadius() {
  2265. const chart = this.chart;
  2266. const chartArea = chart.chartArea;
  2267. const opts = chart.options;
  2268. const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  2269. const outerRadius = Math.max(minSize / 2, 0);
  2270. const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
  2271. const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
  2272. this.outerRadius = outerRadius - radiusLength * this.index;
  2273. this.innerRadius = this.outerRadius - radiusLength;
  2274. }
  2275. updateElements(arcs, start, count, mode) {
  2276. const reset = mode === 'reset';
  2277. const chart = this.chart;
  2278. const opts = chart.options;
  2279. const animationOpts = opts.animation;
  2280. const scale = this._cachedMeta.rScale;
  2281. const centerX = scale.xCenter;
  2282. const centerY = scale.yCenter;
  2283. const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * helpers_segment.PI;
  2284. let angle = datasetStartAngle;
  2285. let i;
  2286. const defaultAngle = 360 / this.countVisibleElements();
  2287. for(i = 0; i < start; ++i){
  2288. angle += this._computeAngle(i, mode, defaultAngle);
  2289. }
  2290. for(i = start; i < start + count; i++){
  2291. const arc = arcs[i];
  2292. let startAngle = angle;
  2293. let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
  2294. let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
  2295. angle = endAngle;
  2296. if (reset) {
  2297. if (animationOpts.animateScale) {
  2298. outerRadius = 0;
  2299. }
  2300. if (animationOpts.animateRotate) {
  2301. startAngle = endAngle = datasetStartAngle;
  2302. }
  2303. }
  2304. const properties = {
  2305. x: centerX,
  2306. y: centerY,
  2307. innerRadius: 0,
  2308. outerRadius,
  2309. startAngle,
  2310. endAngle,
  2311. options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
  2312. };
  2313. this.updateElement(arc, i, properties, mode);
  2314. }
  2315. }
  2316. countVisibleElements() {
  2317. const meta = this._cachedMeta;
  2318. let count = 0;
  2319. meta.data.forEach((element, index)=>{
  2320. if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
  2321. count++;
  2322. }
  2323. });
  2324. return count;
  2325. }
  2326. _computeAngle(index, mode, defaultAngle) {
  2327. return this.chart.getDataVisibility(index) ? helpers_segment.toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
  2328. }
  2329. }
  2330. class PieController extends DoughnutController {
  2331. static id = 'pie';
  2332. static defaults = {
  2333. cutout: 0,
  2334. rotation: 0,
  2335. circumference: 360,
  2336. radius: '100%'
  2337. };
  2338. }
  2339. class RadarController extends DatasetController {
  2340. static id = 'radar';
  2341. static defaults = {
  2342. datasetElementType: 'line',
  2343. dataElementType: 'point',
  2344. indexAxis: 'r',
  2345. showLine: true,
  2346. elements: {
  2347. line: {
  2348. fill: 'start'
  2349. }
  2350. }
  2351. };
  2352. static overrides = {
  2353. aspectRatio: 1,
  2354. scales: {
  2355. r: {
  2356. type: 'radialLinear'
  2357. }
  2358. }
  2359. };
  2360. getLabelAndValue(index) {
  2361. const vScale = this._cachedMeta.vScale;
  2362. const parsed = this.getParsed(index);
  2363. return {
  2364. label: vScale.getLabels()[index],
  2365. value: '' + vScale.getLabelForValue(parsed[vScale.axis])
  2366. };
  2367. }
  2368. parseObjectData(meta, data, start, count) {
  2369. return helpers_segment._parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  2370. }
  2371. update(mode) {
  2372. const meta = this._cachedMeta;
  2373. const line = meta.dataset;
  2374. const points = meta.data || [];
  2375. const labels = meta.iScale.getLabels();
  2376. line.points = points;
  2377. if (mode !== 'resize') {
  2378. const options = this.resolveDatasetElementOptions(mode);
  2379. if (!this.options.showLine) {
  2380. options.borderWidth = 0;
  2381. }
  2382. const properties = {
  2383. _loop: true,
  2384. _fullLoop: labels.length === points.length,
  2385. options
  2386. };
  2387. this.updateElement(line, undefined, properties, mode);
  2388. }
  2389. this.updateElements(points, 0, points.length, mode);
  2390. }
  2391. updateElements(points, start, count, mode) {
  2392. const scale = this._cachedMeta.rScale;
  2393. const reset = mode === 'reset';
  2394. for(let i = start; i < start + count; i++){
  2395. const point = points[i];
  2396. const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2397. const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
  2398. const x = reset ? scale.xCenter : pointPosition.x;
  2399. const y = reset ? scale.yCenter : pointPosition.y;
  2400. const properties = {
  2401. x,
  2402. y,
  2403. angle: pointPosition.angle,
  2404. skip: isNaN(x) || isNaN(y),
  2405. options
  2406. };
  2407. this.updateElement(point, i, properties, mode);
  2408. }
  2409. }
  2410. }
  2411. class ScatterController extends DatasetController {
  2412. static id = 'scatter';
  2413. static defaults = {
  2414. datasetElementType: false,
  2415. dataElementType: 'point',
  2416. showLine: false,
  2417. fill: false
  2418. };
  2419. static overrides = {
  2420. interaction: {
  2421. mode: 'point'
  2422. },
  2423. scales: {
  2424. x: {
  2425. type: 'linear'
  2426. },
  2427. y: {
  2428. type: 'linear'
  2429. }
  2430. }
  2431. };
  2432. getLabelAndValue(index) {
  2433. const meta = this._cachedMeta;
  2434. const labels = this.chart.data.labels || [];
  2435. const { xScale , yScale } = meta;
  2436. const parsed = this.getParsed(index);
  2437. const x = xScale.getLabelForValue(parsed.x);
  2438. const y = yScale.getLabelForValue(parsed.y);
  2439. return {
  2440. label: labels[index] || '',
  2441. value: '(' + x + ', ' + y + ')'
  2442. };
  2443. }
  2444. update(mode) {
  2445. const meta = this._cachedMeta;
  2446. const { data: points = [] } = meta;
  2447. const animationsDisabled = this.chart._animationsDisabled;
  2448. let { start , count } = helpers_segment._getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  2449. this._drawStart = start;
  2450. this._drawCount = count;
  2451. if (helpers_segment._scaleRangesChanged(meta)) {
  2452. start = 0;
  2453. count = points.length;
  2454. }
  2455. if (this.options.showLine) {
  2456. if (!this.datasetElementType) {
  2457. this.addElements();
  2458. }
  2459. const { dataset: line , _dataset } = meta;
  2460. line._chart = this.chart;
  2461. line._datasetIndex = this.index;
  2462. line._decimated = !!_dataset._decimated;
  2463. line.points = points;
  2464. const options = this.resolveDatasetElementOptions(mode);
  2465. options.segment = this.options.segment;
  2466. this.updateElement(line, undefined, {
  2467. animated: !animationsDisabled,
  2468. options
  2469. }, mode);
  2470. } else if (this.datasetElementType) {
  2471. delete meta.dataset;
  2472. this.datasetElementType = false;
  2473. }
  2474. this.updateElements(points, start, count, mode);
  2475. }
  2476. addElements() {
  2477. const { showLine } = this.options;
  2478. if (!this.datasetElementType && showLine) {
  2479. this.datasetElementType = this.chart.registry.getElement('line');
  2480. }
  2481. super.addElements();
  2482. }
  2483. updateElements(points, start, count, mode) {
  2484. const reset = mode === 'reset';
  2485. const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
  2486. const firstOpts = this.resolveDataElementOptions(start, mode);
  2487. const sharedOptions = this.getSharedOptions(firstOpts);
  2488. const includeOptions = this.includeOptions(mode, sharedOptions);
  2489. const iAxis = iScale.axis;
  2490. const vAxis = vScale.axis;
  2491. const { spanGaps , segment } = this.options;
  2492. const maxGapLength = helpers_segment.isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  2493. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  2494. let prevParsed = start > 0 && this.getParsed(start - 1);
  2495. for(let i = start; i < start + count; ++i){
  2496. const point = points[i];
  2497. const parsed = this.getParsed(i);
  2498. const properties = directUpdate ? point : {};
  2499. const nullData = helpers_segment.isNullOrUndef(parsed[vAxis]);
  2500. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  2501. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  2502. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  2503. properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  2504. if (segment) {
  2505. properties.parsed = parsed;
  2506. properties.raw = _dataset.data[i];
  2507. }
  2508. if (includeOptions) {
  2509. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  2510. }
  2511. if (!directUpdate) {
  2512. this.updateElement(point, i, properties, mode);
  2513. }
  2514. prevParsed = parsed;
  2515. }
  2516. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  2517. }
  2518. getMaxOverflow() {
  2519. const meta = this._cachedMeta;
  2520. const data = meta.data || [];
  2521. if (!this.options.showLine) {
  2522. let max = 0;
  2523. for(let i = data.length - 1; i >= 0; --i){
  2524. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  2525. }
  2526. return max > 0 && max;
  2527. }
  2528. const dataset = meta.dataset;
  2529. const border = dataset.options && dataset.options.borderWidth || 0;
  2530. if (!data.length) {
  2531. return border;
  2532. }
  2533. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  2534. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  2535. return Math.max(border, firstPoint, lastPoint) / 2;
  2536. }
  2537. }
  2538. var controllers = /*#__PURE__*/Object.freeze({
  2539. __proto__: null,
  2540. BarController: BarController,
  2541. BubbleController: BubbleController,
  2542. DoughnutController: DoughnutController,
  2543. LineController: LineController,
  2544. PieController: PieController,
  2545. PolarAreaController: PolarAreaController,
  2546. RadarController: RadarController,
  2547. ScatterController: ScatterController
  2548. });
  2549. /**
  2550. * @namespace Chart._adapters
  2551. * @since 2.8.0
  2552. * @private
  2553. */ function abstract() {
  2554. throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  2555. }
  2556. /**
  2557. * Date adapter (current used by the time scale)
  2558. * @namespace Chart._adapters._date
  2559. * @memberof Chart._adapters
  2560. * @private
  2561. */ class DateAdapterBase {
  2562. /**
  2563. * Override default date adapter methods.
  2564. * Accepts type parameter to define options type.
  2565. * @example
  2566. * Chart._adapters._date.override<{myAdapterOption: string}>({
  2567. * init() {
  2568. * console.log(this.options.myAdapterOption);
  2569. * }
  2570. * })
  2571. */ static override(members) {
  2572. Object.assign(DateAdapterBase.prototype, members);
  2573. }
  2574. options;
  2575. constructor(options){
  2576. this.options = options || {};
  2577. }
  2578. // eslint-disable-next-line @typescript-eslint/no-empty-function
  2579. init() {}
  2580. formats() {
  2581. return abstract();
  2582. }
  2583. parse() {
  2584. return abstract();
  2585. }
  2586. format() {
  2587. return abstract();
  2588. }
  2589. add() {
  2590. return abstract();
  2591. }
  2592. diff() {
  2593. return abstract();
  2594. }
  2595. startOf() {
  2596. return abstract();
  2597. }
  2598. endOf() {
  2599. return abstract();
  2600. }
  2601. }
  2602. var adapters = {
  2603. _date: DateAdapterBase
  2604. };
  2605. function binarySearch(metaset, axis, value, intersect) {
  2606. const { controller , data , _sorted } = metaset;
  2607. const iScale = controller._cachedMeta.iScale;
  2608. if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
  2609. const lookupMethod = iScale._reversePixels ? helpers_segment._rlookupByKey : helpers_segment._lookupByKey;
  2610. if (!intersect) {
  2611. return lookupMethod(data, axis, value);
  2612. } else if (controller._sharedOptions) {
  2613. const el = data[0];
  2614. const range = typeof el.getRange === 'function' && el.getRange(axis);
  2615. if (range) {
  2616. const start = lookupMethod(data, axis, value - range);
  2617. const end = lookupMethod(data, axis, value + range);
  2618. return {
  2619. lo: start.lo,
  2620. hi: end.hi
  2621. };
  2622. }
  2623. }
  2624. }
  2625. return {
  2626. lo: 0,
  2627. hi: data.length - 1
  2628. };
  2629. }
  2630. function evaluateInteractionItems(chart, axis, position, handler, intersect) {
  2631. const metasets = chart.getSortedVisibleDatasetMetas();
  2632. const value = position[axis];
  2633. for(let i = 0, ilen = metasets.length; i < ilen; ++i){
  2634. const { index , data } = metasets[i];
  2635. const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
  2636. for(let j = lo; j <= hi; ++j){
  2637. const element = data[j];
  2638. if (!element.skip) {
  2639. handler(element, index, j);
  2640. }
  2641. }
  2642. }
  2643. }
  2644. function getDistanceMetricForAxis(axis) {
  2645. const useX = axis.indexOf('x') !== -1;
  2646. const useY = axis.indexOf('y') !== -1;
  2647. return function(pt1, pt2) {
  2648. const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  2649. const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  2650. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  2651. };
  2652. }
  2653. function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
  2654. const items = [];
  2655. if (!includeInvisible && !chart.isPointInArea(position)) {
  2656. return items;
  2657. }
  2658. const evaluationFunc = function(element, datasetIndex, index) {
  2659. if (!includeInvisible && !helpers_segment._isPointInArea(element, chart.chartArea, 0)) {
  2660. return;
  2661. }
  2662. if (element.inRange(position.x, position.y, useFinalPosition)) {
  2663. items.push({
  2664. element,
  2665. datasetIndex,
  2666. index
  2667. });
  2668. }
  2669. };
  2670. evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
  2671. return items;
  2672. }
  2673. function getNearestRadialItems(chart, position, axis, useFinalPosition) {
  2674. let items = [];
  2675. function evaluationFunc(element, datasetIndex, index) {
  2676. const { startAngle , endAngle } = element.getProps([
  2677. 'startAngle',
  2678. 'endAngle'
  2679. ], useFinalPosition);
  2680. const { angle } = helpers_segment.getAngleFromPoint(element, {
  2681. x: position.x,
  2682. y: position.y
  2683. });
  2684. if (helpers_segment._angleBetween(angle, startAngle, endAngle)) {
  2685. items.push({
  2686. element,
  2687. datasetIndex,
  2688. index
  2689. });
  2690. }
  2691. }
  2692. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2693. return items;
  2694. }
  2695. function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2696. let items = [];
  2697. const distanceMetric = getDistanceMetricForAxis(axis);
  2698. let minDistance = Number.POSITIVE_INFINITY;
  2699. function evaluationFunc(element, datasetIndex, index) {
  2700. const inRange = element.inRange(position.x, position.y, useFinalPosition);
  2701. if (intersect && !inRange) {
  2702. return;
  2703. }
  2704. const center = element.getCenterPoint(useFinalPosition);
  2705. const pointInArea = !!includeInvisible || chart.isPointInArea(center);
  2706. if (!pointInArea && !inRange) {
  2707. return;
  2708. }
  2709. const distance = distanceMetric(position, center);
  2710. if (distance < minDistance) {
  2711. items = [
  2712. {
  2713. element,
  2714. datasetIndex,
  2715. index
  2716. }
  2717. ];
  2718. minDistance = distance;
  2719. } else if (distance === minDistance) {
  2720. items.push({
  2721. element,
  2722. datasetIndex,
  2723. index
  2724. });
  2725. }
  2726. }
  2727. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  2728. return items;
  2729. }
  2730. function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
  2731. if (!includeInvisible && !chart.isPointInArea(position)) {
  2732. return [];
  2733. }
  2734. return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
  2735. }
  2736. function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
  2737. const items = [];
  2738. const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
  2739. let intersectsItem = false;
  2740. evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
  2741. if (element[rangeMethod](position[axis], useFinalPosition)) {
  2742. items.push({
  2743. element,
  2744. datasetIndex,
  2745. index
  2746. });
  2747. intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
  2748. }
  2749. });
  2750. if (intersect && !intersectsItem) {
  2751. return [];
  2752. }
  2753. return items;
  2754. }
  2755. var Interaction = {
  2756. evaluateInteractionItems,
  2757. modes: {
  2758. index (chart, e, options, useFinalPosition) {
  2759. const position = helpers_segment.getRelativePosition(e, chart);
  2760. const axis = options.axis || 'x';
  2761. const includeInvisible = options.includeInvisible || false;
  2762. const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2763. const elements = [];
  2764. if (!items.length) {
  2765. return [];
  2766. }
  2767. chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
  2768. const index = items[0].index;
  2769. const element = meta.data[index];
  2770. if (element && !element.skip) {
  2771. elements.push({
  2772. element,
  2773. datasetIndex: meta.index,
  2774. index
  2775. });
  2776. }
  2777. });
  2778. return elements;
  2779. },
  2780. dataset (chart, e, options, useFinalPosition) {
  2781. const position = helpers_segment.getRelativePosition(e, chart);
  2782. const axis = options.axis || 'xy';
  2783. const includeInvisible = options.includeInvisible || false;
  2784. let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
  2785. if (items.length > 0) {
  2786. const datasetIndex = items[0].datasetIndex;
  2787. const data = chart.getDatasetMeta(datasetIndex).data;
  2788. items = [];
  2789. for(let i = 0; i < data.length; ++i){
  2790. items.push({
  2791. element: data[i],
  2792. datasetIndex,
  2793. index: i
  2794. });
  2795. }
  2796. }
  2797. return items;
  2798. },
  2799. point (chart, e, options, useFinalPosition) {
  2800. const position = helpers_segment.getRelativePosition(e, chart);
  2801. const axis = options.axis || 'xy';
  2802. const includeInvisible = options.includeInvisible || false;
  2803. return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
  2804. },
  2805. nearest (chart, e, options, useFinalPosition) {
  2806. const position = helpers_segment.getRelativePosition(e, chart);
  2807. const axis = options.axis || 'xy';
  2808. const includeInvisible = options.includeInvisible || false;
  2809. return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
  2810. },
  2811. x (chart, e, options, useFinalPosition) {
  2812. const position = helpers_segment.getRelativePosition(e, chart);
  2813. return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
  2814. },
  2815. y (chart, e, options, useFinalPosition) {
  2816. const position = helpers_segment.getRelativePosition(e, chart);
  2817. return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
  2818. }
  2819. }
  2820. };
  2821. const STATIC_POSITIONS = [
  2822. 'left',
  2823. 'top',
  2824. 'right',
  2825. 'bottom'
  2826. ];
  2827. function filterByPosition(array, position) {
  2828. return array.filter((v)=>v.pos === position);
  2829. }
  2830. function filterDynamicPositionByAxis(array, axis) {
  2831. return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
  2832. }
  2833. function sortByWeight(array, reverse) {
  2834. return array.sort((a, b)=>{
  2835. const v0 = reverse ? b : a;
  2836. const v1 = reverse ? a : b;
  2837. return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
  2838. });
  2839. }
  2840. function wrapBoxes(boxes) {
  2841. const layoutBoxes = [];
  2842. let i, ilen, box, pos, stack, stackWeight;
  2843. for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
  2844. box = boxes[i];
  2845. ({ position: pos , options: { stack , stackWeight =1 } } = box);
  2846. layoutBoxes.push({
  2847. index: i,
  2848. box,
  2849. pos,
  2850. horizontal: box.isHorizontal(),
  2851. weight: box.weight,
  2852. stack: stack && pos + stack,
  2853. stackWeight
  2854. });
  2855. }
  2856. return layoutBoxes;
  2857. }
  2858. function buildStacks(layouts) {
  2859. const stacks = {};
  2860. for (const wrap of layouts){
  2861. const { stack , pos , stackWeight } = wrap;
  2862. if (!stack || !STATIC_POSITIONS.includes(pos)) {
  2863. continue;
  2864. }
  2865. const _stack = stacks[stack] || (stacks[stack] = {
  2866. count: 0,
  2867. placed: 0,
  2868. weight: 0,
  2869. size: 0
  2870. });
  2871. _stack.count++;
  2872. _stack.weight += stackWeight;
  2873. }
  2874. return stacks;
  2875. }
  2876. function setLayoutDims(layouts, params) {
  2877. const stacks = buildStacks(layouts);
  2878. const { vBoxMaxWidth , hBoxMaxHeight } = params;
  2879. let i, ilen, layout;
  2880. for(i = 0, ilen = layouts.length; i < ilen; ++i){
  2881. layout = layouts[i];
  2882. const { fullSize } = layout.box;
  2883. const stack = stacks[layout.stack];
  2884. const factor = stack && layout.stackWeight / stack.weight;
  2885. if (layout.horizontal) {
  2886. layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
  2887. layout.height = hBoxMaxHeight;
  2888. } else {
  2889. layout.width = vBoxMaxWidth;
  2890. layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
  2891. }
  2892. }
  2893. return stacks;
  2894. }
  2895. function buildLayoutBoxes(boxes) {
  2896. const layoutBoxes = wrapBoxes(boxes);
  2897. const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
  2898. const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
  2899. const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
  2900. const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
  2901. const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
  2902. const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
  2903. const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
  2904. return {
  2905. fullSize,
  2906. leftAndTop: left.concat(top),
  2907. rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
  2908. chartArea: filterByPosition(layoutBoxes, 'chartArea'),
  2909. vertical: left.concat(right).concat(centerVertical),
  2910. horizontal: top.concat(bottom).concat(centerHorizontal)
  2911. };
  2912. }
  2913. function getCombinedMax(maxPadding, chartArea, a, b) {
  2914. return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  2915. }
  2916. function updateMaxPadding(maxPadding, boxPadding) {
  2917. maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
  2918. maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
  2919. maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
  2920. maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  2921. }
  2922. function updateDims(chartArea, params, layout, stacks) {
  2923. const { pos , box } = layout;
  2924. const maxPadding = chartArea.maxPadding;
  2925. if (!helpers_segment.isObject(pos)) {
  2926. if (layout.size) {
  2927. chartArea[pos] -= layout.size;
  2928. }
  2929. const stack = stacks[layout.stack] || {
  2930. size: 0,
  2931. count: 1
  2932. };
  2933. stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
  2934. layout.size = stack.size / stack.count;
  2935. chartArea[pos] += layout.size;
  2936. }
  2937. if (box.getPadding) {
  2938. updateMaxPadding(maxPadding, box.getPadding());
  2939. }
  2940. const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
  2941. const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
  2942. const widthChanged = newWidth !== chartArea.w;
  2943. const heightChanged = newHeight !== chartArea.h;
  2944. chartArea.w = newWidth;
  2945. chartArea.h = newHeight;
  2946. return layout.horizontal ? {
  2947. same: widthChanged,
  2948. other: heightChanged
  2949. } : {
  2950. same: heightChanged,
  2951. other: widthChanged
  2952. };
  2953. }
  2954. function handleMaxPadding(chartArea) {
  2955. const maxPadding = chartArea.maxPadding;
  2956. function updatePos(pos) {
  2957. const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
  2958. chartArea[pos] += change;
  2959. return change;
  2960. }
  2961. chartArea.y += updatePos('top');
  2962. chartArea.x += updatePos('left');
  2963. updatePos('right');
  2964. updatePos('bottom');
  2965. }
  2966. function getMargins(horizontal, chartArea) {
  2967. const maxPadding = chartArea.maxPadding;
  2968. function marginForPositions(positions) {
  2969. const margin = {
  2970. left: 0,
  2971. top: 0,
  2972. right: 0,
  2973. bottom: 0
  2974. };
  2975. positions.forEach((pos)=>{
  2976. margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
  2977. });
  2978. return margin;
  2979. }
  2980. return horizontal ? marginForPositions([
  2981. 'left',
  2982. 'right'
  2983. ]) : marginForPositions([
  2984. 'top',
  2985. 'bottom'
  2986. ]);
  2987. }
  2988. function fitBoxes(boxes, chartArea, params, stacks) {
  2989. const refitBoxes = [];
  2990. let i, ilen, layout, box, refit, changed;
  2991. for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
  2992. layout = boxes[i];
  2993. box = layout.box;
  2994. box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
  2995. const { same , other } = updateDims(chartArea, params, layout, stacks);
  2996. refit |= same && refitBoxes.length;
  2997. changed = changed || other;
  2998. if (!box.fullSize) {
  2999. refitBoxes.push(layout);
  3000. }
  3001. }
  3002. return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  3003. }
  3004. function setBoxDims(box, left, top, width, height) {
  3005. box.top = top;
  3006. box.left = left;
  3007. box.right = left + width;
  3008. box.bottom = top + height;
  3009. box.width = width;
  3010. box.height = height;
  3011. }
  3012. function placeBoxes(boxes, chartArea, params, stacks) {
  3013. const userPadding = params.padding;
  3014. let { x , y } = chartArea;
  3015. for (const layout of boxes){
  3016. const box = layout.box;
  3017. const stack = stacks[layout.stack] || {
  3018. count: 1,
  3019. placed: 0,
  3020. weight: 1
  3021. };
  3022. const weight = layout.stackWeight / stack.weight || 1;
  3023. if (layout.horizontal) {
  3024. const width = chartArea.w * weight;
  3025. const height = stack.size || box.height;
  3026. if (helpers_segment.defined(stack.start)) {
  3027. y = stack.start;
  3028. }
  3029. if (box.fullSize) {
  3030. setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
  3031. } else {
  3032. setBoxDims(box, chartArea.left + stack.placed, y, width, height);
  3033. }
  3034. stack.start = y;
  3035. stack.placed += width;
  3036. y = box.bottom;
  3037. } else {
  3038. const height = chartArea.h * weight;
  3039. const width = stack.size || box.width;
  3040. if (helpers_segment.defined(stack.start)) {
  3041. x = stack.start;
  3042. }
  3043. if (box.fullSize) {
  3044. setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
  3045. } else {
  3046. setBoxDims(box, x, chartArea.top + stack.placed, width, height);
  3047. }
  3048. stack.start = x;
  3049. stack.placed += height;
  3050. x = box.right;
  3051. }
  3052. }
  3053. chartArea.x = x;
  3054. chartArea.y = y;
  3055. }
  3056. var layouts = {
  3057. addBox (chart, item) {
  3058. if (!chart.boxes) {
  3059. chart.boxes = [];
  3060. }
  3061. item.fullSize = item.fullSize || false;
  3062. item.position = item.position || 'top';
  3063. item.weight = item.weight || 0;
  3064. item._layers = item._layers || function() {
  3065. return [
  3066. {
  3067. z: 0,
  3068. draw (chartArea) {
  3069. item.draw(chartArea);
  3070. }
  3071. }
  3072. ];
  3073. };
  3074. chart.boxes.push(item);
  3075. },
  3076. removeBox (chart, layoutItem) {
  3077. const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
  3078. if (index !== -1) {
  3079. chart.boxes.splice(index, 1);
  3080. }
  3081. },
  3082. configure (chart, item, options) {
  3083. item.fullSize = options.fullSize;
  3084. item.position = options.position;
  3085. item.weight = options.weight;
  3086. },
  3087. update (chart, width, height, minPadding) {
  3088. if (!chart) {
  3089. return;
  3090. }
  3091. const padding = helpers_segment.toPadding(chart.options.layout.padding);
  3092. const availableWidth = Math.max(width - padding.width, 0);
  3093. const availableHeight = Math.max(height - padding.height, 0);
  3094. const boxes = buildLayoutBoxes(chart.boxes);
  3095. const verticalBoxes = boxes.vertical;
  3096. const horizontalBoxes = boxes.horizontal;
  3097. helpers_segment.each(chart.boxes, (box)=>{
  3098. if (typeof box.beforeLayout === 'function') {
  3099. box.beforeLayout();
  3100. }
  3101. });
  3102. const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
  3103. const params = Object.freeze({
  3104. outerWidth: width,
  3105. outerHeight: height,
  3106. padding,
  3107. availableWidth,
  3108. availableHeight,
  3109. vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
  3110. hBoxMaxHeight: availableHeight / 2
  3111. });
  3112. const maxPadding = Object.assign({}, padding);
  3113. updateMaxPadding(maxPadding, helpers_segment.toPadding(minPadding));
  3114. const chartArea = Object.assign({
  3115. maxPadding,
  3116. w: availableWidth,
  3117. h: availableHeight,
  3118. x: padding.left,
  3119. y: padding.top
  3120. }, padding);
  3121. const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
  3122. fitBoxes(boxes.fullSize, chartArea, params, stacks);
  3123. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3124. if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
  3125. fitBoxes(verticalBoxes, chartArea, params, stacks);
  3126. }
  3127. handleMaxPadding(chartArea);
  3128. placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
  3129. chartArea.x += chartArea.w;
  3130. chartArea.y += chartArea.h;
  3131. placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
  3132. chart.chartArea = {
  3133. left: chartArea.left,
  3134. top: chartArea.top,
  3135. right: chartArea.left + chartArea.w,
  3136. bottom: chartArea.top + chartArea.h,
  3137. height: chartArea.h,
  3138. width: chartArea.w
  3139. };
  3140. helpers_segment.each(boxes.chartArea, (layout)=>{
  3141. const box = layout.box;
  3142. Object.assign(box, chart.chartArea);
  3143. box.update(chartArea.w, chartArea.h, {
  3144. left: 0,
  3145. top: 0,
  3146. right: 0,
  3147. bottom: 0
  3148. });
  3149. });
  3150. }
  3151. };
  3152. class BasePlatform {
  3153. acquireContext(canvas, aspectRatio) {}
  3154. releaseContext(context) {
  3155. return false;
  3156. }
  3157. addEventListener(chart, type, listener) {}
  3158. removeEventListener(chart, type, listener) {}
  3159. getDevicePixelRatio() {
  3160. return 1;
  3161. }
  3162. getMaximumSize(element, width, height, aspectRatio) {
  3163. width = Math.max(0, width || element.width);
  3164. height = height || element.height;
  3165. return {
  3166. width,
  3167. height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
  3168. };
  3169. }
  3170. isAttached(canvas) {
  3171. return true;
  3172. }
  3173. updateConfig(config) {
  3174. }
  3175. }
  3176. class BasicPlatform extends BasePlatform {
  3177. acquireContext(item) {
  3178. return item && item.getContext && item.getContext('2d') || null;
  3179. }
  3180. updateConfig(config) {
  3181. config.options.animation = false;
  3182. }
  3183. }
  3184. const EXPANDO_KEY = '$chartjs';
  3185. const EVENT_TYPES = {
  3186. touchstart: 'mousedown',
  3187. touchmove: 'mousemove',
  3188. touchend: 'mouseup',
  3189. pointerenter: 'mouseenter',
  3190. pointerdown: 'mousedown',
  3191. pointermove: 'mousemove',
  3192. pointerup: 'mouseup',
  3193. pointerleave: 'mouseout',
  3194. pointerout: 'mouseout'
  3195. };
  3196. const isNullOrEmpty = (value)=>value === null || value === '';
  3197. function initCanvas(canvas, aspectRatio) {
  3198. const style = canvas.style;
  3199. const renderHeight = canvas.getAttribute('height');
  3200. const renderWidth = canvas.getAttribute('width');
  3201. canvas[EXPANDO_KEY] = {
  3202. initial: {
  3203. height: renderHeight,
  3204. width: renderWidth,
  3205. style: {
  3206. display: style.display,
  3207. height: style.height,
  3208. width: style.width
  3209. }
  3210. }
  3211. };
  3212. style.display = style.display || 'block';
  3213. style.boxSizing = style.boxSizing || 'border-box';
  3214. if (isNullOrEmpty(renderWidth)) {
  3215. const displayWidth = helpers_segment.readUsedSize(canvas, 'width');
  3216. if (displayWidth !== undefined) {
  3217. canvas.width = displayWidth;
  3218. }
  3219. }
  3220. if (isNullOrEmpty(renderHeight)) {
  3221. if (canvas.style.height === '') {
  3222. canvas.height = canvas.width / (aspectRatio || 2);
  3223. } else {
  3224. const displayHeight = helpers_segment.readUsedSize(canvas, 'height');
  3225. if (displayHeight !== undefined) {
  3226. canvas.height = displayHeight;
  3227. }
  3228. }
  3229. }
  3230. return canvas;
  3231. }
  3232. const eventListenerOptions = helpers_segment.supportsEventListenerOptions ? {
  3233. passive: true
  3234. } : false;
  3235. function addListener(node, type, listener) {
  3236. node.addEventListener(type, listener, eventListenerOptions);
  3237. }
  3238. function removeListener(chart, type, listener) {
  3239. chart.canvas.removeEventListener(type, listener, eventListenerOptions);
  3240. }
  3241. function fromNativeEvent(event, chart) {
  3242. const type = EVENT_TYPES[event.type] || event.type;
  3243. const { x , y } = helpers_segment.getRelativePosition(event, chart);
  3244. return {
  3245. type,
  3246. chart,
  3247. native: event,
  3248. x: x !== undefined ? x : null,
  3249. y: y !== undefined ? y : null
  3250. };
  3251. }
  3252. function nodeListContains(nodeList, canvas) {
  3253. for (const node of nodeList){
  3254. if (node === canvas || node.contains(canvas)) {
  3255. return true;
  3256. }
  3257. }
  3258. }
  3259. function createAttachObserver(chart, type, listener) {
  3260. const canvas = chart.canvas;
  3261. const observer = new MutationObserver((entries)=>{
  3262. let trigger = false;
  3263. for (const entry of entries){
  3264. trigger = trigger || nodeListContains(entry.addedNodes, canvas);
  3265. trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
  3266. }
  3267. if (trigger) {
  3268. listener();
  3269. }
  3270. });
  3271. observer.observe(document, {
  3272. childList: true,
  3273. subtree: true
  3274. });
  3275. return observer;
  3276. }
  3277. function createDetachObserver(chart, type, listener) {
  3278. const canvas = chart.canvas;
  3279. const observer = new MutationObserver((entries)=>{
  3280. let trigger = false;
  3281. for (const entry of entries){
  3282. trigger = trigger || nodeListContains(entry.removedNodes, canvas);
  3283. trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
  3284. }
  3285. if (trigger) {
  3286. listener();
  3287. }
  3288. });
  3289. observer.observe(document, {
  3290. childList: true,
  3291. subtree: true
  3292. });
  3293. return observer;
  3294. }
  3295. const drpListeningCharts = new Map();
  3296. let oldDevicePixelRatio = 0;
  3297. function onWindowResize() {
  3298. const dpr = window.devicePixelRatio;
  3299. if (dpr === oldDevicePixelRatio) {
  3300. return;
  3301. }
  3302. oldDevicePixelRatio = dpr;
  3303. drpListeningCharts.forEach((resize, chart)=>{
  3304. if (chart.currentDevicePixelRatio !== dpr) {
  3305. resize();
  3306. }
  3307. });
  3308. }
  3309. function listenDevicePixelRatioChanges(chart, resize) {
  3310. if (!drpListeningCharts.size) {
  3311. window.addEventListener('resize', onWindowResize);
  3312. }
  3313. drpListeningCharts.set(chart, resize);
  3314. }
  3315. function unlistenDevicePixelRatioChanges(chart) {
  3316. drpListeningCharts.delete(chart);
  3317. if (!drpListeningCharts.size) {
  3318. window.removeEventListener('resize', onWindowResize);
  3319. }
  3320. }
  3321. function createResizeObserver(chart, type, listener) {
  3322. const canvas = chart.canvas;
  3323. const container = canvas && helpers_segment._getParentNode(canvas);
  3324. if (!container) {
  3325. return;
  3326. }
  3327. const resize = helpers_segment.throttled((width, height)=>{
  3328. const w = container.clientWidth;
  3329. listener(width, height);
  3330. if (w < container.clientWidth) {
  3331. listener();
  3332. }
  3333. }, window);
  3334. const observer = new ResizeObserver((entries)=>{
  3335. const entry = entries[0];
  3336. const width = entry.contentRect.width;
  3337. const height = entry.contentRect.height;
  3338. if (width === 0 && height === 0) {
  3339. return;
  3340. }
  3341. resize(width, height);
  3342. });
  3343. observer.observe(container);
  3344. listenDevicePixelRatioChanges(chart, resize);
  3345. return observer;
  3346. }
  3347. function releaseObserver(chart, type, observer) {
  3348. if (observer) {
  3349. observer.disconnect();
  3350. }
  3351. if (type === 'resize') {
  3352. unlistenDevicePixelRatioChanges(chart);
  3353. }
  3354. }
  3355. function createProxyAndListen(chart, type, listener) {
  3356. const canvas = chart.canvas;
  3357. const proxy = helpers_segment.throttled((event)=>{
  3358. if (chart.ctx !== null) {
  3359. listener(fromNativeEvent(event, chart));
  3360. }
  3361. }, chart);
  3362. addListener(canvas, type, proxy);
  3363. return proxy;
  3364. }
  3365. class DomPlatform extends BasePlatform {
  3366. acquireContext(canvas, aspectRatio) {
  3367. const context = canvas && canvas.getContext && canvas.getContext('2d');
  3368. if (context && context.canvas === canvas) {
  3369. initCanvas(canvas, aspectRatio);
  3370. return context;
  3371. }
  3372. return null;
  3373. }
  3374. releaseContext(context) {
  3375. const canvas = context.canvas;
  3376. if (!canvas[EXPANDO_KEY]) {
  3377. return false;
  3378. }
  3379. const initial = canvas[EXPANDO_KEY].initial;
  3380. [
  3381. 'height',
  3382. 'width'
  3383. ].forEach((prop)=>{
  3384. const value = initial[prop];
  3385. if (helpers_segment.isNullOrUndef(value)) {
  3386. canvas.removeAttribute(prop);
  3387. } else {
  3388. canvas.setAttribute(prop, value);
  3389. }
  3390. });
  3391. const style = initial.style || {};
  3392. Object.keys(style).forEach((key)=>{
  3393. canvas.style[key] = style[key];
  3394. });
  3395. canvas.width = canvas.width;
  3396. delete canvas[EXPANDO_KEY];
  3397. return true;
  3398. }
  3399. addEventListener(chart, type, listener) {
  3400. this.removeEventListener(chart, type);
  3401. const proxies = chart.$proxies || (chart.$proxies = {});
  3402. const handlers = {
  3403. attach: createAttachObserver,
  3404. detach: createDetachObserver,
  3405. resize: createResizeObserver
  3406. };
  3407. const handler = handlers[type] || createProxyAndListen;
  3408. proxies[type] = handler(chart, type, listener);
  3409. }
  3410. removeEventListener(chart, type) {
  3411. const proxies = chart.$proxies || (chart.$proxies = {});
  3412. const proxy = proxies[type];
  3413. if (!proxy) {
  3414. return;
  3415. }
  3416. const handlers = {
  3417. attach: releaseObserver,
  3418. detach: releaseObserver,
  3419. resize: releaseObserver
  3420. };
  3421. const handler = handlers[type] || removeListener;
  3422. handler(chart, type, proxy);
  3423. proxies[type] = undefined;
  3424. }
  3425. getDevicePixelRatio() {
  3426. return window.devicePixelRatio;
  3427. }
  3428. getMaximumSize(canvas, width, height, aspectRatio) {
  3429. return helpers_segment.getMaximumSize(canvas, width, height, aspectRatio);
  3430. }
  3431. isAttached(canvas) {
  3432. const container = helpers_segment._getParentNode(canvas);
  3433. return !!(container && container.isConnected);
  3434. }
  3435. }
  3436. function _detectPlatform(canvas) {
  3437. if (!helpers_segment._isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
  3438. return BasicPlatform;
  3439. }
  3440. return DomPlatform;
  3441. }
  3442. class Element {
  3443. static defaults = {};
  3444. static defaultRoutes = undefined;
  3445. x;
  3446. y;
  3447. active = false;
  3448. options;
  3449. $animations;
  3450. tooltipPosition(useFinalPosition) {
  3451. const { x , y } = this.getProps([
  3452. 'x',
  3453. 'y'
  3454. ], useFinalPosition);
  3455. return {
  3456. x,
  3457. y
  3458. };
  3459. }
  3460. hasValue() {
  3461. return helpers_segment.isNumber(this.x) && helpers_segment.isNumber(this.y);
  3462. }
  3463. getProps(props, final) {
  3464. const anims = this.$animations;
  3465. if (!final || !anims) {
  3466. // let's not create an object, if not needed
  3467. return this;
  3468. }
  3469. const ret = {};
  3470. props.forEach((prop)=>{
  3471. ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
  3472. });
  3473. return ret;
  3474. }
  3475. }
  3476. function autoSkip(scale, ticks) {
  3477. const tickOpts = scale.options.ticks;
  3478. const determinedMaxTicks = determineMaxTicks(scale);
  3479. const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
  3480. const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
  3481. const numMajorIndices = majorIndices.length;
  3482. const first = majorIndices[0];
  3483. const last = majorIndices[numMajorIndices - 1];
  3484. const newTicks = [];
  3485. if (numMajorIndices > ticksLimit) {
  3486. skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
  3487. return newTicks;
  3488. }
  3489. const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
  3490. if (numMajorIndices > 0) {
  3491. let i, ilen;
  3492. const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
  3493. skip(ticks, newTicks, spacing, helpers_segment.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
  3494. for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
  3495. skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
  3496. }
  3497. skip(ticks, newTicks, spacing, last, helpers_segment.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
  3498. return newTicks;
  3499. }
  3500. skip(ticks, newTicks, spacing);
  3501. return newTicks;
  3502. }
  3503. function determineMaxTicks(scale) {
  3504. const offset = scale.options.offset;
  3505. const tickLength = scale._tickSize();
  3506. const maxScale = scale._length / tickLength + (offset ? 0 : 1);
  3507. const maxChart = scale._maxLength / tickLength;
  3508. return Math.floor(Math.min(maxScale, maxChart));
  3509. }
  3510. function calculateSpacing(majorIndices, ticks, ticksLimit) {
  3511. const evenMajorSpacing = getEvenSpacing(majorIndices);
  3512. const spacing = ticks.length / ticksLimit;
  3513. if (!evenMajorSpacing) {
  3514. return Math.max(spacing, 1);
  3515. }
  3516. const factors = helpers_segment._factorize(evenMajorSpacing);
  3517. for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
  3518. const factor = factors[i];
  3519. if (factor > spacing) {
  3520. return factor;
  3521. }
  3522. }
  3523. return Math.max(spacing, 1);
  3524. }
  3525. function getMajorIndices(ticks) {
  3526. const result = [];
  3527. let i, ilen;
  3528. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3529. if (ticks[i].major) {
  3530. result.push(i);
  3531. }
  3532. }
  3533. return result;
  3534. }
  3535. function skipMajors(ticks, newTicks, majorIndices, spacing) {
  3536. let count = 0;
  3537. let next = majorIndices[0];
  3538. let i;
  3539. spacing = Math.ceil(spacing);
  3540. for(i = 0; i < ticks.length; i++){
  3541. if (i === next) {
  3542. newTicks.push(ticks[i]);
  3543. count++;
  3544. next = majorIndices[count * spacing];
  3545. }
  3546. }
  3547. }
  3548. function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
  3549. const start = helpers_segment.valueOrDefault(majorStart, 0);
  3550. const end = Math.min(helpers_segment.valueOrDefault(majorEnd, ticks.length), ticks.length);
  3551. let count = 0;
  3552. let length, i, next;
  3553. spacing = Math.ceil(spacing);
  3554. if (majorEnd) {
  3555. length = majorEnd - majorStart;
  3556. spacing = length / Math.floor(length / spacing);
  3557. }
  3558. next = start;
  3559. while(next < 0){
  3560. count++;
  3561. next = Math.round(start + count * spacing);
  3562. }
  3563. for(i = Math.max(start, 0); i < end; i++){
  3564. if (i === next) {
  3565. newTicks.push(ticks[i]);
  3566. count++;
  3567. next = Math.round(start + count * spacing);
  3568. }
  3569. }
  3570. }
  3571. function getEvenSpacing(arr) {
  3572. const len = arr.length;
  3573. let i, diff;
  3574. if (len < 2) {
  3575. return false;
  3576. }
  3577. for(diff = arr[0], i = 1; i < len; ++i){
  3578. if (arr[i] - arr[i - 1] !== diff) {
  3579. return false;
  3580. }
  3581. }
  3582. return diff;
  3583. }
  3584. const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  3585. const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  3586. const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
  3587. function sample(arr, numItems) {
  3588. const result = [];
  3589. const increment = arr.length / numItems;
  3590. const len = arr.length;
  3591. let i = 0;
  3592. for(; i < len; i += increment){
  3593. result.push(arr[Math.floor(i)]);
  3594. }
  3595. return result;
  3596. }
  3597. function getPixelForGridLine(scale, index, offsetGridLines) {
  3598. const length = scale.ticks.length;
  3599. const validIndex = Math.min(index, length - 1);
  3600. const start = scale._startPixel;
  3601. const end = scale._endPixel;
  3602. const epsilon = 1e-6;
  3603. let lineValue = scale.getPixelForTick(validIndex);
  3604. let offset;
  3605. if (offsetGridLines) {
  3606. if (length === 1) {
  3607. offset = Math.max(lineValue - start, end - lineValue);
  3608. } else if (index === 0) {
  3609. offset = (scale.getPixelForTick(1) - lineValue) / 2;
  3610. } else {
  3611. offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
  3612. }
  3613. lineValue += validIndex < index ? offset : -offset;
  3614. if (lineValue < start - epsilon || lineValue > end + epsilon) {
  3615. return;
  3616. }
  3617. }
  3618. return lineValue;
  3619. }
  3620. function garbageCollect(caches, length) {
  3621. helpers_segment.each(caches, (cache)=>{
  3622. const gc = cache.gc;
  3623. const gcLen = gc.length / 2;
  3624. let i;
  3625. if (gcLen > length) {
  3626. for(i = 0; i < gcLen; ++i){
  3627. delete cache.data[gc[i]];
  3628. }
  3629. gc.splice(0, gcLen);
  3630. }
  3631. });
  3632. }
  3633. function getTickMarkLength(options) {
  3634. return options.drawTicks ? options.tickLength : 0;
  3635. }
  3636. function getTitleHeight(options, fallback) {
  3637. if (!options.display) {
  3638. return 0;
  3639. }
  3640. const font = helpers_segment.toFont(options.font, fallback);
  3641. const padding = helpers_segment.toPadding(options.padding);
  3642. const lines = helpers_segment.isArray(options.text) ? options.text.length : 1;
  3643. return lines * font.lineHeight + padding.height;
  3644. }
  3645. function createScaleContext(parent, scale) {
  3646. return helpers_segment.createContext(parent, {
  3647. scale,
  3648. type: 'scale'
  3649. });
  3650. }
  3651. function createTickContext(parent, index, tick) {
  3652. return helpers_segment.createContext(parent, {
  3653. tick,
  3654. index,
  3655. type: 'tick'
  3656. });
  3657. }
  3658. function titleAlign(align, position, reverse) {
  3659. let ret = helpers_segment._toLeftRightCenter(align);
  3660. if (reverse && position !== 'right' || !reverse && position === 'right') {
  3661. ret = reverseAlign(ret);
  3662. }
  3663. return ret;
  3664. }
  3665. function titleArgs(scale, offset, position, align) {
  3666. const { top , left , bottom , right , chart } = scale;
  3667. const { chartArea , scales } = chart;
  3668. let rotation = 0;
  3669. let maxWidth, titleX, titleY;
  3670. const height = bottom - top;
  3671. const width = right - left;
  3672. if (scale.isHorizontal()) {
  3673. titleX = helpers_segment._alignStartEnd(align, left, right);
  3674. if (helpers_segment.isObject(position)) {
  3675. const positionAxisID = Object.keys(position)[0];
  3676. const value = position[positionAxisID];
  3677. titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
  3678. } else if (position === 'center') {
  3679. titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
  3680. } else {
  3681. titleY = offsetFromEdge(scale, position, offset);
  3682. }
  3683. maxWidth = right - left;
  3684. } else {
  3685. if (helpers_segment.isObject(position)) {
  3686. const positionAxisID = Object.keys(position)[0];
  3687. const value = position[positionAxisID];
  3688. titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
  3689. } else if (position === 'center') {
  3690. titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
  3691. } else {
  3692. titleX = offsetFromEdge(scale, position, offset);
  3693. }
  3694. titleY = helpers_segment._alignStartEnd(align, bottom, top);
  3695. rotation = position === 'left' ? -helpers_segment.HALF_PI : helpers_segment.HALF_PI;
  3696. }
  3697. return {
  3698. titleX,
  3699. titleY,
  3700. maxWidth,
  3701. rotation
  3702. };
  3703. }
  3704. class Scale extends Element {
  3705. constructor(cfg){
  3706. super();
  3707. this.id = cfg.id;
  3708. this.type = cfg.type;
  3709. this.options = undefined;
  3710. this.ctx = cfg.ctx;
  3711. this.chart = cfg.chart;
  3712. this.top = undefined;
  3713. this.bottom = undefined;
  3714. this.left = undefined;
  3715. this.right = undefined;
  3716. this.width = undefined;
  3717. this.height = undefined;
  3718. this._margins = {
  3719. left: 0,
  3720. right: 0,
  3721. top: 0,
  3722. bottom: 0
  3723. };
  3724. this.maxWidth = undefined;
  3725. this.maxHeight = undefined;
  3726. this.paddingTop = undefined;
  3727. this.paddingBottom = undefined;
  3728. this.paddingLeft = undefined;
  3729. this.paddingRight = undefined;
  3730. this.axis = undefined;
  3731. this.labelRotation = undefined;
  3732. this.min = undefined;
  3733. this.max = undefined;
  3734. this._range = undefined;
  3735. this.ticks = [];
  3736. this._gridLineItems = null;
  3737. this._labelItems = null;
  3738. this._labelSizes = null;
  3739. this._length = 0;
  3740. this._maxLength = 0;
  3741. this._longestTextCache = {};
  3742. this._startPixel = undefined;
  3743. this._endPixel = undefined;
  3744. this._reversePixels = false;
  3745. this._userMax = undefined;
  3746. this._userMin = undefined;
  3747. this._suggestedMax = undefined;
  3748. this._suggestedMin = undefined;
  3749. this._ticksLength = 0;
  3750. this._borderValue = 0;
  3751. this._cache = {};
  3752. this._dataLimitsCached = false;
  3753. this.$context = undefined;
  3754. }
  3755. init(options) {
  3756. this.options = options.setContext(this.getContext());
  3757. this.axis = options.axis;
  3758. this._userMin = this.parse(options.min);
  3759. this._userMax = this.parse(options.max);
  3760. this._suggestedMin = this.parse(options.suggestedMin);
  3761. this._suggestedMax = this.parse(options.suggestedMax);
  3762. }
  3763. parse(raw, index) {
  3764. return raw;
  3765. }
  3766. getUserBounds() {
  3767. let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
  3768. _userMin = helpers_segment.finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
  3769. _userMax = helpers_segment.finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
  3770. _suggestedMin = helpers_segment.finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
  3771. _suggestedMax = helpers_segment.finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
  3772. return {
  3773. min: helpers_segment.finiteOrDefault(_userMin, _suggestedMin),
  3774. max: helpers_segment.finiteOrDefault(_userMax, _suggestedMax),
  3775. minDefined: helpers_segment.isNumberFinite(_userMin),
  3776. maxDefined: helpers_segment.isNumberFinite(_userMax)
  3777. };
  3778. }
  3779. getMinMax(canStack) {
  3780. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  3781. let range;
  3782. if (minDefined && maxDefined) {
  3783. return {
  3784. min,
  3785. max
  3786. };
  3787. }
  3788. const metas = this.getMatchingVisibleMetas();
  3789. for(let i = 0, ilen = metas.length; i < ilen; ++i){
  3790. range = metas[i].controller.getMinMax(this, canStack);
  3791. if (!minDefined) {
  3792. min = Math.min(min, range.min);
  3793. }
  3794. if (!maxDefined) {
  3795. max = Math.max(max, range.max);
  3796. }
  3797. }
  3798. min = maxDefined && min > max ? max : min;
  3799. max = minDefined && min > max ? min : max;
  3800. return {
  3801. min: helpers_segment.finiteOrDefault(min, helpers_segment.finiteOrDefault(max, min)),
  3802. max: helpers_segment.finiteOrDefault(max, helpers_segment.finiteOrDefault(min, max))
  3803. };
  3804. }
  3805. getPadding() {
  3806. return {
  3807. left: this.paddingLeft || 0,
  3808. top: this.paddingTop || 0,
  3809. right: this.paddingRight || 0,
  3810. bottom: this.paddingBottom || 0
  3811. };
  3812. }
  3813. getTicks() {
  3814. return this.ticks;
  3815. }
  3816. getLabels() {
  3817. const data = this.chart.data;
  3818. return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
  3819. }
  3820. getLabelItems(chartArea = this.chart.chartArea) {
  3821. const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
  3822. return items;
  3823. }
  3824. beforeLayout() {
  3825. this._cache = {};
  3826. this._dataLimitsCached = false;
  3827. }
  3828. beforeUpdate() {
  3829. helpers_segment.callback(this.options.beforeUpdate, [
  3830. this
  3831. ]);
  3832. }
  3833. update(maxWidth, maxHeight, margins) {
  3834. const { beginAtZero , grace , ticks: tickOpts } = this.options;
  3835. const sampleSize = tickOpts.sampleSize;
  3836. this.beforeUpdate();
  3837. this.maxWidth = maxWidth;
  3838. this.maxHeight = maxHeight;
  3839. this._margins = margins = Object.assign({
  3840. left: 0,
  3841. right: 0,
  3842. top: 0,
  3843. bottom: 0
  3844. }, margins);
  3845. this.ticks = null;
  3846. this._labelSizes = null;
  3847. this._gridLineItems = null;
  3848. this._labelItems = null;
  3849. this.beforeSetDimensions();
  3850. this.setDimensions();
  3851. this.afterSetDimensions();
  3852. this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
  3853. if (!this._dataLimitsCached) {
  3854. this.beforeDataLimits();
  3855. this.determineDataLimits();
  3856. this.afterDataLimits();
  3857. this._range = helpers_segment._addGrace(this, grace, beginAtZero);
  3858. this._dataLimitsCached = true;
  3859. }
  3860. this.beforeBuildTicks();
  3861. this.ticks = this.buildTicks() || [];
  3862. this.afterBuildTicks();
  3863. const samplingEnabled = sampleSize < this.ticks.length;
  3864. this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
  3865. this.configure();
  3866. this.beforeCalculateLabelRotation();
  3867. this.calculateLabelRotation();
  3868. this.afterCalculateLabelRotation();
  3869. if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
  3870. this.ticks = autoSkip(this, this.ticks);
  3871. this._labelSizes = null;
  3872. this.afterAutoSkip();
  3873. }
  3874. if (samplingEnabled) {
  3875. this._convertTicksToLabels(this.ticks);
  3876. }
  3877. this.beforeFit();
  3878. this.fit();
  3879. this.afterFit();
  3880. this.afterUpdate();
  3881. }
  3882. configure() {
  3883. let reversePixels = this.options.reverse;
  3884. let startPixel, endPixel;
  3885. if (this.isHorizontal()) {
  3886. startPixel = this.left;
  3887. endPixel = this.right;
  3888. } else {
  3889. startPixel = this.top;
  3890. endPixel = this.bottom;
  3891. reversePixels = !reversePixels;
  3892. }
  3893. this._startPixel = startPixel;
  3894. this._endPixel = endPixel;
  3895. this._reversePixels = reversePixels;
  3896. this._length = endPixel - startPixel;
  3897. this._alignToPixels = this.options.alignToPixels;
  3898. }
  3899. afterUpdate() {
  3900. helpers_segment.callback(this.options.afterUpdate, [
  3901. this
  3902. ]);
  3903. }
  3904. beforeSetDimensions() {
  3905. helpers_segment.callback(this.options.beforeSetDimensions, [
  3906. this
  3907. ]);
  3908. }
  3909. setDimensions() {
  3910. if (this.isHorizontal()) {
  3911. this.width = this.maxWidth;
  3912. this.left = 0;
  3913. this.right = this.width;
  3914. } else {
  3915. this.height = this.maxHeight;
  3916. this.top = 0;
  3917. this.bottom = this.height;
  3918. }
  3919. this.paddingLeft = 0;
  3920. this.paddingTop = 0;
  3921. this.paddingRight = 0;
  3922. this.paddingBottom = 0;
  3923. }
  3924. afterSetDimensions() {
  3925. helpers_segment.callback(this.options.afterSetDimensions, [
  3926. this
  3927. ]);
  3928. }
  3929. _callHooks(name) {
  3930. this.chart.notifyPlugins(name, this.getContext());
  3931. helpers_segment.callback(this.options[name], [
  3932. this
  3933. ]);
  3934. }
  3935. beforeDataLimits() {
  3936. this._callHooks('beforeDataLimits');
  3937. }
  3938. determineDataLimits() {}
  3939. afterDataLimits() {
  3940. this._callHooks('afterDataLimits');
  3941. }
  3942. beforeBuildTicks() {
  3943. this._callHooks('beforeBuildTicks');
  3944. }
  3945. buildTicks() {
  3946. return [];
  3947. }
  3948. afterBuildTicks() {
  3949. this._callHooks('afterBuildTicks');
  3950. }
  3951. beforeTickToLabelConversion() {
  3952. helpers_segment.callback(this.options.beforeTickToLabelConversion, [
  3953. this
  3954. ]);
  3955. }
  3956. generateTickLabels(ticks) {
  3957. const tickOpts = this.options.ticks;
  3958. let i, ilen, tick;
  3959. for(i = 0, ilen = ticks.length; i < ilen; i++){
  3960. tick = ticks[i];
  3961. tick.label = helpers_segment.callback(tickOpts.callback, [
  3962. tick.value,
  3963. i,
  3964. ticks
  3965. ], this);
  3966. }
  3967. }
  3968. afterTickToLabelConversion() {
  3969. helpers_segment.callback(this.options.afterTickToLabelConversion, [
  3970. this
  3971. ]);
  3972. }
  3973. beforeCalculateLabelRotation() {
  3974. helpers_segment.callback(this.options.beforeCalculateLabelRotation, [
  3975. this
  3976. ]);
  3977. }
  3978. calculateLabelRotation() {
  3979. const options = this.options;
  3980. const tickOpts = options.ticks;
  3981. const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
  3982. const minRotation = tickOpts.minRotation || 0;
  3983. const maxRotation = tickOpts.maxRotation;
  3984. let labelRotation = minRotation;
  3985. let tickWidth, maxHeight, maxLabelDiagonal;
  3986. if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
  3987. this.labelRotation = minRotation;
  3988. return;
  3989. }
  3990. const labelSizes = this._getLabelSizes();
  3991. const maxLabelWidth = labelSizes.widest.width;
  3992. const maxLabelHeight = labelSizes.highest.height;
  3993. const maxWidth = helpers_segment._limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
  3994. tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
  3995. if (maxLabelWidth + 6 > tickWidth) {
  3996. tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
  3997. maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
  3998. maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
  3999. labelRotation = helpers_segment.toDegrees(Math.min(Math.asin(helpers_segment._limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(helpers_segment._limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(helpers_segment._limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
  4000. labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
  4001. }
  4002. this.labelRotation = labelRotation;
  4003. }
  4004. afterCalculateLabelRotation() {
  4005. helpers_segment.callback(this.options.afterCalculateLabelRotation, [
  4006. this
  4007. ]);
  4008. }
  4009. afterAutoSkip() {}
  4010. beforeFit() {
  4011. helpers_segment.callback(this.options.beforeFit, [
  4012. this
  4013. ]);
  4014. }
  4015. fit() {
  4016. const minSize = {
  4017. width: 0,
  4018. height: 0
  4019. };
  4020. const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
  4021. const display = this._isVisible();
  4022. const isHorizontal = this.isHorizontal();
  4023. if (display) {
  4024. const titleHeight = getTitleHeight(titleOpts, chart.options.font);
  4025. if (isHorizontal) {
  4026. minSize.width = this.maxWidth;
  4027. minSize.height = getTickMarkLength(gridOpts) + titleHeight;
  4028. } else {
  4029. minSize.height = this.maxHeight;
  4030. minSize.width = getTickMarkLength(gridOpts) + titleHeight;
  4031. }
  4032. if (tickOpts.display && this.ticks.length) {
  4033. const { first , last , widest , highest } = this._getLabelSizes();
  4034. const tickPadding = tickOpts.padding * 2;
  4035. const angleRadians = helpers_segment.toRadians(this.labelRotation);
  4036. const cos = Math.cos(angleRadians);
  4037. const sin = Math.sin(angleRadians);
  4038. if (isHorizontal) {
  4039. const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
  4040. minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
  4041. } else {
  4042. const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
  4043. minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
  4044. }
  4045. this._calculatePadding(first, last, sin, cos);
  4046. }
  4047. }
  4048. this._handleMargins();
  4049. if (isHorizontal) {
  4050. this.width = this._length = chart.width - this._margins.left - this._margins.right;
  4051. this.height = minSize.height;
  4052. } else {
  4053. this.width = minSize.width;
  4054. this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
  4055. }
  4056. }
  4057. _calculatePadding(first, last, sin, cos) {
  4058. const { ticks: { align , padding } , position } = this.options;
  4059. const isRotated = this.labelRotation !== 0;
  4060. const labelsBelowTicks = position !== 'top' && this.axis === 'x';
  4061. if (this.isHorizontal()) {
  4062. const offsetLeft = this.getPixelForTick(0) - this.left;
  4063. const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
  4064. let paddingLeft = 0;
  4065. let paddingRight = 0;
  4066. if (isRotated) {
  4067. if (labelsBelowTicks) {
  4068. paddingLeft = cos * first.width;
  4069. paddingRight = sin * last.height;
  4070. } else {
  4071. paddingLeft = sin * first.height;
  4072. paddingRight = cos * last.width;
  4073. }
  4074. } else if (align === 'start') {
  4075. paddingRight = last.width;
  4076. } else if (align === 'end') {
  4077. paddingLeft = first.width;
  4078. } else if (align !== 'inner') {
  4079. paddingLeft = first.width / 2;
  4080. paddingRight = last.width / 2;
  4081. }
  4082. this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
  4083. this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
  4084. } else {
  4085. let paddingTop = last.height / 2;
  4086. let paddingBottom = first.height / 2;
  4087. if (align === 'start') {
  4088. paddingTop = 0;
  4089. paddingBottom = first.height;
  4090. } else if (align === 'end') {
  4091. paddingTop = last.height;
  4092. paddingBottom = 0;
  4093. }
  4094. this.paddingTop = paddingTop + padding;
  4095. this.paddingBottom = paddingBottom + padding;
  4096. }
  4097. }
  4098. _handleMargins() {
  4099. if (this._margins) {
  4100. this._margins.left = Math.max(this.paddingLeft, this._margins.left);
  4101. this._margins.top = Math.max(this.paddingTop, this._margins.top);
  4102. this._margins.right = Math.max(this.paddingRight, this._margins.right);
  4103. this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
  4104. }
  4105. }
  4106. afterFit() {
  4107. helpers_segment.callback(this.options.afterFit, [
  4108. this
  4109. ]);
  4110. }
  4111. isHorizontal() {
  4112. const { axis , position } = this.options;
  4113. return position === 'top' || position === 'bottom' || axis === 'x';
  4114. }
  4115. isFullSize() {
  4116. return this.options.fullSize;
  4117. }
  4118. _convertTicksToLabels(ticks) {
  4119. this.beforeTickToLabelConversion();
  4120. this.generateTickLabels(ticks);
  4121. let i, ilen;
  4122. for(i = 0, ilen = ticks.length; i < ilen; i++){
  4123. if (helpers_segment.isNullOrUndef(ticks[i].label)) {
  4124. ticks.splice(i, 1);
  4125. ilen--;
  4126. i--;
  4127. }
  4128. }
  4129. this.afterTickToLabelConversion();
  4130. }
  4131. _getLabelSizes() {
  4132. let labelSizes = this._labelSizes;
  4133. if (!labelSizes) {
  4134. const sampleSize = this.options.ticks.sampleSize;
  4135. let ticks = this.ticks;
  4136. if (sampleSize < ticks.length) {
  4137. ticks = sample(ticks, sampleSize);
  4138. }
  4139. this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
  4140. }
  4141. return labelSizes;
  4142. }
  4143. _computeLabelSizes(ticks, length, maxTicksLimit) {
  4144. const { ctx , _longestTextCache: caches } = this;
  4145. const widths = [];
  4146. const heights = [];
  4147. const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
  4148. let widestLabelSize = 0;
  4149. let highestLabelSize = 0;
  4150. let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
  4151. for(i = 0; i < length; i += increment){
  4152. label = ticks[i].label;
  4153. tickFont = this._resolveTickFontOptions(i);
  4154. ctx.font = fontString = tickFont.string;
  4155. cache = caches[fontString] = caches[fontString] || {
  4156. data: {},
  4157. gc: []
  4158. };
  4159. lineHeight = tickFont.lineHeight;
  4160. width = height = 0;
  4161. if (!helpers_segment.isNullOrUndef(label) && !helpers_segment.isArray(label)) {
  4162. width = helpers_segment._measureText(ctx, cache.data, cache.gc, width, label);
  4163. height = lineHeight;
  4164. } else if (helpers_segment.isArray(label)) {
  4165. for(j = 0, jlen = label.length; j < jlen; ++j){
  4166. nestedLabel = label[j];
  4167. if (!helpers_segment.isNullOrUndef(nestedLabel) && !helpers_segment.isArray(nestedLabel)) {
  4168. width = helpers_segment._measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  4169. height += lineHeight;
  4170. }
  4171. }
  4172. }
  4173. widths.push(width);
  4174. heights.push(height);
  4175. widestLabelSize = Math.max(width, widestLabelSize);
  4176. highestLabelSize = Math.max(height, highestLabelSize);
  4177. }
  4178. garbageCollect(caches, length);
  4179. const widest = widths.indexOf(widestLabelSize);
  4180. const highest = heights.indexOf(highestLabelSize);
  4181. const valueAt = (idx)=>({
  4182. width: widths[idx] || 0,
  4183. height: heights[idx] || 0
  4184. });
  4185. return {
  4186. first: valueAt(0),
  4187. last: valueAt(length - 1),
  4188. widest: valueAt(widest),
  4189. highest: valueAt(highest),
  4190. widths,
  4191. heights
  4192. };
  4193. }
  4194. getLabelForValue(value) {
  4195. return value;
  4196. }
  4197. getPixelForValue(value, index) {
  4198. return NaN;
  4199. }
  4200. getValueForPixel(pixel) {}
  4201. getPixelForTick(index) {
  4202. const ticks = this.ticks;
  4203. if (index < 0 || index > ticks.length - 1) {
  4204. return null;
  4205. }
  4206. return this.getPixelForValue(ticks[index].value);
  4207. }
  4208. getPixelForDecimal(decimal) {
  4209. if (this._reversePixels) {
  4210. decimal = 1 - decimal;
  4211. }
  4212. const pixel = this._startPixel + decimal * this._length;
  4213. return helpers_segment._int16Range(this._alignToPixels ? helpers_segment._alignPixel(this.chart, pixel, 0) : pixel);
  4214. }
  4215. getDecimalForPixel(pixel) {
  4216. const decimal = (pixel - this._startPixel) / this._length;
  4217. return this._reversePixels ? 1 - decimal : decimal;
  4218. }
  4219. getBasePixel() {
  4220. return this.getPixelForValue(this.getBaseValue());
  4221. }
  4222. getBaseValue() {
  4223. const { min , max } = this;
  4224. return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
  4225. }
  4226. getContext(index) {
  4227. const ticks = this.ticks || [];
  4228. if (index >= 0 && index < ticks.length) {
  4229. const tick = ticks[index];
  4230. return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
  4231. }
  4232. return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
  4233. }
  4234. _tickSize() {
  4235. const optionTicks = this.options.ticks;
  4236. const rot = helpers_segment.toRadians(this.labelRotation);
  4237. const cos = Math.abs(Math.cos(rot));
  4238. const sin = Math.abs(Math.sin(rot));
  4239. const labelSizes = this._getLabelSizes();
  4240. const padding = optionTicks.autoSkipPadding || 0;
  4241. const w = labelSizes ? labelSizes.widest.width + padding : 0;
  4242. const h = labelSizes ? labelSizes.highest.height + padding : 0;
  4243. return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
  4244. }
  4245. _isVisible() {
  4246. const display = this.options.display;
  4247. if (display !== 'auto') {
  4248. return !!display;
  4249. }
  4250. return this.getMatchingVisibleMetas().length > 0;
  4251. }
  4252. _computeGridLineItems(chartArea) {
  4253. const axis = this.axis;
  4254. const chart = this.chart;
  4255. const options = this.options;
  4256. const { grid , position , border } = options;
  4257. const offset = grid.offset;
  4258. const isHorizontal = this.isHorizontal();
  4259. const ticks = this.ticks;
  4260. const ticksLength = ticks.length + (offset ? 1 : 0);
  4261. const tl = getTickMarkLength(grid);
  4262. const items = [];
  4263. const borderOpts = border.setContext(this.getContext());
  4264. const axisWidth = borderOpts.display ? borderOpts.width : 0;
  4265. const axisHalfWidth = axisWidth / 2;
  4266. const alignBorderValue = function(pixel) {
  4267. return helpers_segment._alignPixel(chart, pixel, axisWidth);
  4268. };
  4269. let borderValue, i, lineValue, alignedLineValue;
  4270. let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
  4271. if (position === 'top') {
  4272. borderValue = alignBorderValue(this.bottom);
  4273. ty1 = this.bottom - tl;
  4274. ty2 = borderValue - axisHalfWidth;
  4275. y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
  4276. y2 = chartArea.bottom;
  4277. } else if (position === 'bottom') {
  4278. borderValue = alignBorderValue(this.top);
  4279. y1 = chartArea.top;
  4280. y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
  4281. ty1 = borderValue + axisHalfWidth;
  4282. ty2 = this.top + tl;
  4283. } else if (position === 'left') {
  4284. borderValue = alignBorderValue(this.right);
  4285. tx1 = this.right - tl;
  4286. tx2 = borderValue - axisHalfWidth;
  4287. x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
  4288. x2 = chartArea.right;
  4289. } else if (position === 'right') {
  4290. borderValue = alignBorderValue(this.left);
  4291. x1 = chartArea.left;
  4292. x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
  4293. tx1 = borderValue + axisHalfWidth;
  4294. tx2 = this.left + tl;
  4295. } else if (axis === 'x') {
  4296. if (position === 'center') {
  4297. borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
  4298. } else if (helpers_segment.isObject(position)) {
  4299. const positionAxisID = Object.keys(position)[0];
  4300. const value = position[positionAxisID];
  4301. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  4302. }
  4303. y1 = chartArea.top;
  4304. y2 = chartArea.bottom;
  4305. ty1 = borderValue + axisHalfWidth;
  4306. ty2 = ty1 + tl;
  4307. } else if (axis === 'y') {
  4308. if (position === 'center') {
  4309. borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
  4310. } else if (helpers_segment.isObject(position)) {
  4311. const positionAxisID = Object.keys(position)[0];
  4312. const value = position[positionAxisID];
  4313. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  4314. }
  4315. tx1 = borderValue - axisHalfWidth;
  4316. tx2 = tx1 - tl;
  4317. x1 = chartArea.left;
  4318. x2 = chartArea.right;
  4319. }
  4320. const limit = helpers_segment.valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
  4321. const step = Math.max(1, Math.ceil(ticksLength / limit));
  4322. for(i = 0; i < ticksLength; i += step){
  4323. const context = this.getContext(i);
  4324. const optsAtIndex = grid.setContext(context);
  4325. const optsAtIndexBorder = border.setContext(context);
  4326. const lineWidth = optsAtIndex.lineWidth;
  4327. const lineColor = optsAtIndex.color;
  4328. const borderDash = optsAtIndexBorder.dash || [];
  4329. const borderDashOffset = optsAtIndexBorder.dashOffset;
  4330. const tickWidth = optsAtIndex.tickWidth;
  4331. const tickColor = optsAtIndex.tickColor;
  4332. const tickBorderDash = optsAtIndex.tickBorderDash || [];
  4333. const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
  4334. lineValue = getPixelForGridLine(this, i, offset);
  4335. if (lineValue === undefined) {
  4336. continue;
  4337. }
  4338. alignedLineValue = helpers_segment._alignPixel(chart, lineValue, lineWidth);
  4339. if (isHorizontal) {
  4340. tx1 = tx2 = x1 = x2 = alignedLineValue;
  4341. } else {
  4342. ty1 = ty2 = y1 = y2 = alignedLineValue;
  4343. }
  4344. items.push({
  4345. tx1,
  4346. ty1,
  4347. tx2,
  4348. ty2,
  4349. x1,
  4350. y1,
  4351. x2,
  4352. y2,
  4353. width: lineWidth,
  4354. color: lineColor,
  4355. borderDash,
  4356. borderDashOffset,
  4357. tickWidth,
  4358. tickColor,
  4359. tickBorderDash,
  4360. tickBorderDashOffset
  4361. });
  4362. }
  4363. this._ticksLength = ticksLength;
  4364. this._borderValue = borderValue;
  4365. return items;
  4366. }
  4367. _computeLabelItems(chartArea) {
  4368. const axis = this.axis;
  4369. const options = this.options;
  4370. const { position , ticks: optionTicks } = options;
  4371. const isHorizontal = this.isHorizontal();
  4372. const ticks = this.ticks;
  4373. const { align , crossAlign , padding , mirror } = optionTicks;
  4374. const tl = getTickMarkLength(options.grid);
  4375. const tickAndPadding = tl + padding;
  4376. const hTickAndPadding = mirror ? -padding : tickAndPadding;
  4377. const rotation = -helpers_segment.toRadians(this.labelRotation);
  4378. const items = [];
  4379. let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
  4380. let textBaseline = 'middle';
  4381. if (position === 'top') {
  4382. y = this.bottom - hTickAndPadding;
  4383. textAlign = this._getXAxisLabelAlignment();
  4384. } else if (position === 'bottom') {
  4385. y = this.top + hTickAndPadding;
  4386. textAlign = this._getXAxisLabelAlignment();
  4387. } else if (position === 'left') {
  4388. const ret = this._getYAxisLabelAlignment(tl);
  4389. textAlign = ret.textAlign;
  4390. x = ret.x;
  4391. } else if (position === 'right') {
  4392. const ret = this._getYAxisLabelAlignment(tl);
  4393. textAlign = ret.textAlign;
  4394. x = ret.x;
  4395. } else if (axis === 'x') {
  4396. if (position === 'center') {
  4397. y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
  4398. } else if (helpers_segment.isObject(position)) {
  4399. const positionAxisID = Object.keys(position)[0];
  4400. const value = position[positionAxisID];
  4401. y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
  4402. }
  4403. textAlign = this._getXAxisLabelAlignment();
  4404. } else if (axis === 'y') {
  4405. if (position === 'center') {
  4406. x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
  4407. } else if (helpers_segment.isObject(position)) {
  4408. const positionAxisID = Object.keys(position)[0];
  4409. const value = position[positionAxisID];
  4410. x = this.chart.scales[positionAxisID].getPixelForValue(value);
  4411. }
  4412. textAlign = this._getYAxisLabelAlignment(tl).textAlign;
  4413. }
  4414. if (axis === 'y') {
  4415. if (align === 'start') {
  4416. textBaseline = 'top';
  4417. } else if (align === 'end') {
  4418. textBaseline = 'bottom';
  4419. }
  4420. }
  4421. const labelSizes = this._getLabelSizes();
  4422. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  4423. tick = ticks[i];
  4424. label = tick.label;
  4425. const optsAtIndex = optionTicks.setContext(this.getContext(i));
  4426. pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
  4427. font = this._resolveTickFontOptions(i);
  4428. lineHeight = font.lineHeight;
  4429. lineCount = helpers_segment.isArray(label) ? label.length : 1;
  4430. const halfCount = lineCount / 2;
  4431. const color = optsAtIndex.color;
  4432. const strokeColor = optsAtIndex.textStrokeColor;
  4433. const strokeWidth = optsAtIndex.textStrokeWidth;
  4434. let tickTextAlign = textAlign;
  4435. if (isHorizontal) {
  4436. x = pixel;
  4437. if (textAlign === 'inner') {
  4438. if (i === ilen - 1) {
  4439. tickTextAlign = !this.options.reverse ? 'right' : 'left';
  4440. } else if (i === 0) {
  4441. tickTextAlign = !this.options.reverse ? 'left' : 'right';
  4442. } else {
  4443. tickTextAlign = 'center';
  4444. }
  4445. }
  4446. if (position === 'top') {
  4447. if (crossAlign === 'near' || rotation !== 0) {
  4448. textOffset = -lineCount * lineHeight + lineHeight / 2;
  4449. } else if (crossAlign === 'center') {
  4450. textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
  4451. } else {
  4452. textOffset = -labelSizes.highest.height + lineHeight / 2;
  4453. }
  4454. } else {
  4455. if (crossAlign === 'near' || rotation !== 0) {
  4456. textOffset = lineHeight / 2;
  4457. } else if (crossAlign === 'center') {
  4458. textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
  4459. } else {
  4460. textOffset = labelSizes.highest.height - lineCount * lineHeight;
  4461. }
  4462. }
  4463. if (mirror) {
  4464. textOffset *= -1;
  4465. }
  4466. if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
  4467. x += lineHeight / 2 * Math.sin(rotation);
  4468. }
  4469. } else {
  4470. y = pixel;
  4471. textOffset = (1 - lineCount) * lineHeight / 2;
  4472. }
  4473. let backdrop;
  4474. if (optsAtIndex.showLabelBackdrop) {
  4475. const labelPadding = helpers_segment.toPadding(optsAtIndex.backdropPadding);
  4476. const height = labelSizes.heights[i];
  4477. const width = labelSizes.widths[i];
  4478. let top = textOffset - labelPadding.top;
  4479. let left = 0 - labelPadding.left;
  4480. switch(textBaseline){
  4481. case 'middle':
  4482. top -= height / 2;
  4483. break;
  4484. case 'bottom':
  4485. top -= height;
  4486. break;
  4487. }
  4488. switch(textAlign){
  4489. case 'center':
  4490. left -= width / 2;
  4491. break;
  4492. case 'right':
  4493. left -= width;
  4494. break;
  4495. }
  4496. backdrop = {
  4497. left,
  4498. top,
  4499. width: width + labelPadding.width,
  4500. height: height + labelPadding.height,
  4501. color: optsAtIndex.backdropColor
  4502. };
  4503. }
  4504. items.push({
  4505. label,
  4506. font,
  4507. textOffset,
  4508. options: {
  4509. rotation,
  4510. color,
  4511. strokeColor,
  4512. strokeWidth,
  4513. textAlign: tickTextAlign,
  4514. textBaseline,
  4515. translation: [
  4516. x,
  4517. y
  4518. ],
  4519. backdrop
  4520. }
  4521. });
  4522. }
  4523. return items;
  4524. }
  4525. _getXAxisLabelAlignment() {
  4526. const { position , ticks } = this.options;
  4527. const rotation = -helpers_segment.toRadians(this.labelRotation);
  4528. if (rotation) {
  4529. return position === 'top' ? 'left' : 'right';
  4530. }
  4531. let align = 'center';
  4532. if (ticks.align === 'start') {
  4533. align = 'left';
  4534. } else if (ticks.align === 'end') {
  4535. align = 'right';
  4536. } else if (ticks.align === 'inner') {
  4537. align = 'inner';
  4538. }
  4539. return align;
  4540. }
  4541. _getYAxisLabelAlignment(tl) {
  4542. const { position , ticks: { crossAlign , mirror , padding } } = this.options;
  4543. const labelSizes = this._getLabelSizes();
  4544. const tickAndPadding = tl + padding;
  4545. const widest = labelSizes.widest.width;
  4546. let textAlign;
  4547. let x;
  4548. if (position === 'left') {
  4549. if (mirror) {
  4550. x = this.right + padding;
  4551. if (crossAlign === 'near') {
  4552. textAlign = 'left';
  4553. } else if (crossAlign === 'center') {
  4554. textAlign = 'center';
  4555. x += widest / 2;
  4556. } else {
  4557. textAlign = 'right';
  4558. x += widest;
  4559. }
  4560. } else {
  4561. x = this.right - tickAndPadding;
  4562. if (crossAlign === 'near') {
  4563. textAlign = 'right';
  4564. } else if (crossAlign === 'center') {
  4565. textAlign = 'center';
  4566. x -= widest / 2;
  4567. } else {
  4568. textAlign = 'left';
  4569. x = this.left;
  4570. }
  4571. }
  4572. } else if (position === 'right') {
  4573. if (mirror) {
  4574. x = this.left + padding;
  4575. if (crossAlign === 'near') {
  4576. textAlign = 'right';
  4577. } else if (crossAlign === 'center') {
  4578. textAlign = 'center';
  4579. x -= widest / 2;
  4580. } else {
  4581. textAlign = 'left';
  4582. x -= widest;
  4583. }
  4584. } else {
  4585. x = this.left + tickAndPadding;
  4586. if (crossAlign === 'near') {
  4587. textAlign = 'left';
  4588. } else if (crossAlign === 'center') {
  4589. textAlign = 'center';
  4590. x += widest / 2;
  4591. } else {
  4592. textAlign = 'right';
  4593. x = this.right;
  4594. }
  4595. }
  4596. } else {
  4597. textAlign = 'right';
  4598. }
  4599. return {
  4600. textAlign,
  4601. x
  4602. };
  4603. }
  4604. _computeLabelArea() {
  4605. if (this.options.ticks.mirror) {
  4606. return;
  4607. }
  4608. const chart = this.chart;
  4609. const position = this.options.position;
  4610. if (position === 'left' || position === 'right') {
  4611. return {
  4612. top: 0,
  4613. left: this.left,
  4614. bottom: chart.height,
  4615. right: this.right
  4616. };
  4617. }
  4618. if (position === 'top' || position === 'bottom') {
  4619. return {
  4620. top: this.top,
  4621. left: 0,
  4622. bottom: this.bottom,
  4623. right: chart.width
  4624. };
  4625. }
  4626. }
  4627. drawBackground() {
  4628. const { ctx , options: { backgroundColor } , left , top , width , height } = this;
  4629. if (backgroundColor) {
  4630. ctx.save();
  4631. ctx.fillStyle = backgroundColor;
  4632. ctx.fillRect(left, top, width, height);
  4633. ctx.restore();
  4634. }
  4635. }
  4636. getLineWidthForValue(value) {
  4637. const grid = this.options.grid;
  4638. if (!this._isVisible() || !grid.display) {
  4639. return 0;
  4640. }
  4641. const ticks = this.ticks;
  4642. const index = ticks.findIndex((t)=>t.value === value);
  4643. if (index >= 0) {
  4644. const opts = grid.setContext(this.getContext(index));
  4645. return opts.lineWidth;
  4646. }
  4647. return 0;
  4648. }
  4649. drawGrid(chartArea) {
  4650. const grid = this.options.grid;
  4651. const ctx = this.ctx;
  4652. const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
  4653. let i, ilen;
  4654. const drawLine = (p1, p2, style)=>{
  4655. if (!style.width || !style.color) {
  4656. return;
  4657. }
  4658. ctx.save();
  4659. ctx.lineWidth = style.width;
  4660. ctx.strokeStyle = style.color;
  4661. ctx.setLineDash(style.borderDash || []);
  4662. ctx.lineDashOffset = style.borderDashOffset;
  4663. ctx.beginPath();
  4664. ctx.moveTo(p1.x, p1.y);
  4665. ctx.lineTo(p2.x, p2.y);
  4666. ctx.stroke();
  4667. ctx.restore();
  4668. };
  4669. if (grid.display) {
  4670. for(i = 0, ilen = items.length; i < ilen; ++i){
  4671. const item = items[i];
  4672. if (grid.drawOnChartArea) {
  4673. drawLine({
  4674. x: item.x1,
  4675. y: item.y1
  4676. }, {
  4677. x: item.x2,
  4678. y: item.y2
  4679. }, item);
  4680. }
  4681. if (grid.drawTicks) {
  4682. drawLine({
  4683. x: item.tx1,
  4684. y: item.ty1
  4685. }, {
  4686. x: item.tx2,
  4687. y: item.ty2
  4688. }, {
  4689. color: item.tickColor,
  4690. width: item.tickWidth,
  4691. borderDash: item.tickBorderDash,
  4692. borderDashOffset: item.tickBorderDashOffset
  4693. });
  4694. }
  4695. }
  4696. }
  4697. }
  4698. drawBorder() {
  4699. const { chart , ctx , options: { border , grid } } = this;
  4700. const borderOpts = border.setContext(this.getContext());
  4701. const axisWidth = border.display ? borderOpts.width : 0;
  4702. if (!axisWidth) {
  4703. return;
  4704. }
  4705. const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
  4706. const borderValue = this._borderValue;
  4707. let x1, x2, y1, y2;
  4708. if (this.isHorizontal()) {
  4709. x1 = helpers_segment._alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
  4710. x2 = helpers_segment._alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
  4711. y1 = y2 = borderValue;
  4712. } else {
  4713. y1 = helpers_segment._alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
  4714. y2 = helpers_segment._alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
  4715. x1 = x2 = borderValue;
  4716. }
  4717. ctx.save();
  4718. ctx.lineWidth = borderOpts.width;
  4719. ctx.strokeStyle = borderOpts.color;
  4720. ctx.beginPath();
  4721. ctx.moveTo(x1, y1);
  4722. ctx.lineTo(x2, y2);
  4723. ctx.stroke();
  4724. ctx.restore();
  4725. }
  4726. drawLabels(chartArea) {
  4727. const optionTicks = this.options.ticks;
  4728. if (!optionTicks.display) {
  4729. return;
  4730. }
  4731. const ctx = this.ctx;
  4732. const area = this._computeLabelArea();
  4733. if (area) {
  4734. helpers_segment.clipArea(ctx, area);
  4735. }
  4736. const items = this.getLabelItems(chartArea);
  4737. for (const item of items){
  4738. const renderTextOptions = item.options;
  4739. const tickFont = item.font;
  4740. const label = item.label;
  4741. const y = item.textOffset;
  4742. helpers_segment.renderText(ctx, label, 0, y, tickFont, renderTextOptions);
  4743. }
  4744. if (area) {
  4745. helpers_segment.unclipArea(ctx);
  4746. }
  4747. }
  4748. drawTitle() {
  4749. const { ctx , options: { position , title , reverse } } = this;
  4750. if (!title.display) {
  4751. return;
  4752. }
  4753. const font = helpers_segment.toFont(title.font);
  4754. const padding = helpers_segment.toPadding(title.padding);
  4755. const align = title.align;
  4756. let offset = font.lineHeight / 2;
  4757. if (position === 'bottom' || position === 'center' || helpers_segment.isObject(position)) {
  4758. offset += padding.bottom;
  4759. if (helpers_segment.isArray(title.text)) {
  4760. offset += font.lineHeight * (title.text.length - 1);
  4761. }
  4762. } else {
  4763. offset += padding.top;
  4764. }
  4765. const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
  4766. helpers_segment.renderText(ctx, title.text, 0, 0, font, {
  4767. color: title.color,
  4768. maxWidth,
  4769. rotation,
  4770. textAlign: titleAlign(align, position, reverse),
  4771. textBaseline: 'middle',
  4772. translation: [
  4773. titleX,
  4774. titleY
  4775. ]
  4776. });
  4777. }
  4778. draw(chartArea) {
  4779. if (!this._isVisible()) {
  4780. return;
  4781. }
  4782. this.drawBackground();
  4783. this.drawGrid(chartArea);
  4784. this.drawBorder();
  4785. this.drawTitle();
  4786. this.drawLabels(chartArea);
  4787. }
  4788. _layers() {
  4789. const opts = this.options;
  4790. const tz = opts.ticks && opts.ticks.z || 0;
  4791. const gz = helpers_segment.valueOrDefault(opts.grid && opts.grid.z, -1);
  4792. const bz = helpers_segment.valueOrDefault(opts.border && opts.border.z, 0);
  4793. if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
  4794. return [
  4795. {
  4796. z: tz,
  4797. draw: (chartArea)=>{
  4798. this.draw(chartArea);
  4799. }
  4800. }
  4801. ];
  4802. }
  4803. return [
  4804. {
  4805. z: gz,
  4806. draw: (chartArea)=>{
  4807. this.drawBackground();
  4808. this.drawGrid(chartArea);
  4809. this.drawTitle();
  4810. }
  4811. },
  4812. {
  4813. z: bz,
  4814. draw: ()=>{
  4815. this.drawBorder();
  4816. }
  4817. },
  4818. {
  4819. z: tz,
  4820. draw: (chartArea)=>{
  4821. this.drawLabels(chartArea);
  4822. }
  4823. }
  4824. ];
  4825. }
  4826. getMatchingVisibleMetas(type) {
  4827. const metas = this.chart.getSortedVisibleDatasetMetas();
  4828. const axisID = this.axis + 'AxisID';
  4829. const result = [];
  4830. let i, ilen;
  4831. for(i = 0, ilen = metas.length; i < ilen; ++i){
  4832. const meta = metas[i];
  4833. if (meta[axisID] === this.id && (!type || meta.type === type)) {
  4834. result.push(meta);
  4835. }
  4836. }
  4837. return result;
  4838. }
  4839. _resolveTickFontOptions(index) {
  4840. const opts = this.options.ticks.setContext(this.getContext(index));
  4841. return helpers_segment.toFont(opts.font);
  4842. }
  4843. _maxDigits() {
  4844. const fontSize = this._resolveTickFontOptions(0).lineHeight;
  4845. return (this.isHorizontal() ? this.width : this.height) / fontSize;
  4846. }
  4847. }
  4848. class TypedRegistry {
  4849. constructor(type, scope, override){
  4850. this.type = type;
  4851. this.scope = scope;
  4852. this.override = override;
  4853. this.items = Object.create(null);
  4854. }
  4855. isForType(type) {
  4856. return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
  4857. }
  4858. register(item) {
  4859. const proto = Object.getPrototypeOf(item);
  4860. let parentScope;
  4861. if (isIChartComponent(proto)) {
  4862. parentScope = this.register(proto);
  4863. }
  4864. const items = this.items;
  4865. const id = item.id;
  4866. const scope = this.scope + '.' + id;
  4867. if (!id) {
  4868. throw new Error('class does not have id: ' + item);
  4869. }
  4870. if (id in items) {
  4871. return scope;
  4872. }
  4873. items[id] = item;
  4874. registerDefaults(item, scope, parentScope);
  4875. if (this.override) {
  4876. helpers_segment.defaults.override(item.id, item.overrides);
  4877. }
  4878. return scope;
  4879. }
  4880. get(id) {
  4881. return this.items[id];
  4882. }
  4883. unregister(item) {
  4884. const items = this.items;
  4885. const id = item.id;
  4886. const scope = this.scope;
  4887. if (id in items) {
  4888. delete items[id];
  4889. }
  4890. if (scope && id in helpers_segment.defaults[scope]) {
  4891. delete helpers_segment.defaults[scope][id];
  4892. if (this.override) {
  4893. delete helpers_segment.overrides[id];
  4894. }
  4895. }
  4896. }
  4897. }
  4898. function registerDefaults(item, scope, parentScope) {
  4899. const itemDefaults = helpers_segment.merge(Object.create(null), [
  4900. parentScope ? helpers_segment.defaults.get(parentScope) : {},
  4901. helpers_segment.defaults.get(scope),
  4902. item.defaults
  4903. ]);
  4904. helpers_segment.defaults.set(scope, itemDefaults);
  4905. if (item.defaultRoutes) {
  4906. routeDefaults(scope, item.defaultRoutes);
  4907. }
  4908. if (item.descriptors) {
  4909. helpers_segment.defaults.describe(scope, item.descriptors);
  4910. }
  4911. }
  4912. function routeDefaults(scope, routes) {
  4913. Object.keys(routes).forEach((property)=>{
  4914. const propertyParts = property.split('.');
  4915. const sourceName = propertyParts.pop();
  4916. const sourceScope = [
  4917. scope
  4918. ].concat(propertyParts).join('.');
  4919. const parts = routes[property].split('.');
  4920. const targetName = parts.pop();
  4921. const targetScope = parts.join('.');
  4922. helpers_segment.defaults.route(sourceScope, sourceName, targetScope, targetName);
  4923. });
  4924. }
  4925. function isIChartComponent(proto) {
  4926. return 'id' in proto && 'defaults' in proto;
  4927. }
  4928. class Registry {
  4929. constructor(){
  4930. this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
  4931. this.elements = new TypedRegistry(Element, 'elements');
  4932. this.plugins = new TypedRegistry(Object, 'plugins');
  4933. this.scales = new TypedRegistry(Scale, 'scales');
  4934. this._typedRegistries = [
  4935. this.controllers,
  4936. this.scales,
  4937. this.elements
  4938. ];
  4939. }
  4940. add(...args) {
  4941. this._each('register', args);
  4942. }
  4943. remove(...args) {
  4944. this._each('unregister', args);
  4945. }
  4946. addControllers(...args) {
  4947. this._each('register', args, this.controllers);
  4948. }
  4949. addElements(...args) {
  4950. this._each('register', args, this.elements);
  4951. }
  4952. addPlugins(...args) {
  4953. this._each('register', args, this.plugins);
  4954. }
  4955. addScales(...args) {
  4956. this._each('register', args, this.scales);
  4957. }
  4958. getController(id) {
  4959. return this._get(id, this.controllers, 'controller');
  4960. }
  4961. getElement(id) {
  4962. return this._get(id, this.elements, 'element');
  4963. }
  4964. getPlugin(id) {
  4965. return this._get(id, this.plugins, 'plugin');
  4966. }
  4967. getScale(id) {
  4968. return this._get(id, this.scales, 'scale');
  4969. }
  4970. removeControllers(...args) {
  4971. this._each('unregister', args, this.controllers);
  4972. }
  4973. removeElements(...args) {
  4974. this._each('unregister', args, this.elements);
  4975. }
  4976. removePlugins(...args) {
  4977. this._each('unregister', args, this.plugins);
  4978. }
  4979. removeScales(...args) {
  4980. this._each('unregister', args, this.scales);
  4981. }
  4982. _each(method, args, typedRegistry) {
  4983. [
  4984. ...args
  4985. ].forEach((arg)=>{
  4986. const reg = typedRegistry || this._getRegistryForType(arg);
  4987. if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
  4988. this._exec(method, reg, arg);
  4989. } else {
  4990. helpers_segment.each(arg, (item)=>{
  4991. const itemReg = typedRegistry || this._getRegistryForType(item);
  4992. this._exec(method, itemReg, item);
  4993. });
  4994. }
  4995. });
  4996. }
  4997. _exec(method, registry, component) {
  4998. const camelMethod = helpers_segment._capitalize(method);
  4999. helpers_segment.callback(component['before' + camelMethod], [], component);
  5000. registry[method](component);
  5001. helpers_segment.callback(component['after' + camelMethod], [], component);
  5002. }
  5003. _getRegistryForType(type) {
  5004. for(let i = 0; i < this._typedRegistries.length; i++){
  5005. const reg = this._typedRegistries[i];
  5006. if (reg.isForType(type)) {
  5007. return reg;
  5008. }
  5009. }
  5010. return this.plugins;
  5011. }
  5012. _get(id, typedRegistry, type) {
  5013. const item = typedRegistry.get(id);
  5014. if (item === undefined) {
  5015. throw new Error('"' + id + '" is not a registered ' + type + '.');
  5016. }
  5017. return item;
  5018. }
  5019. }
  5020. var registry = /* #__PURE__ */ new Registry();
  5021. class PluginService {
  5022. constructor(){
  5023. this._init = [];
  5024. }
  5025. notify(chart, hook, args, filter) {
  5026. if (hook === 'beforeInit') {
  5027. this._init = this._createDescriptors(chart, true);
  5028. this._notify(this._init, chart, 'install');
  5029. }
  5030. const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
  5031. const result = this._notify(descriptors, chart, hook, args);
  5032. if (hook === 'afterDestroy') {
  5033. this._notify(descriptors, chart, 'stop');
  5034. this._notify(this._init, chart, 'uninstall');
  5035. }
  5036. return result;
  5037. }
  5038. _notify(descriptors, chart, hook, args) {
  5039. args = args || {};
  5040. for (const descriptor of descriptors){
  5041. const plugin = descriptor.plugin;
  5042. const method = plugin[hook];
  5043. const params = [
  5044. chart,
  5045. args,
  5046. descriptor.options
  5047. ];
  5048. if (helpers_segment.callback(method, params, plugin) === false && args.cancelable) {
  5049. return false;
  5050. }
  5051. }
  5052. return true;
  5053. }
  5054. invalidate() {
  5055. if (!helpers_segment.isNullOrUndef(this._cache)) {
  5056. this._oldCache = this._cache;
  5057. this._cache = undefined;
  5058. }
  5059. }
  5060. _descriptors(chart) {
  5061. if (this._cache) {
  5062. return this._cache;
  5063. }
  5064. const descriptors = this._cache = this._createDescriptors(chart);
  5065. this._notifyStateChanges(chart);
  5066. return descriptors;
  5067. }
  5068. _createDescriptors(chart, all) {
  5069. const config = chart && chart.config;
  5070. const options = helpers_segment.valueOrDefault(config.options && config.options.plugins, {});
  5071. const plugins = allPlugins(config);
  5072. return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
  5073. }
  5074. _notifyStateChanges(chart) {
  5075. const previousDescriptors = this._oldCache || [];
  5076. const descriptors = this._cache;
  5077. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
  5078. this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
  5079. this._notify(diff(descriptors, previousDescriptors), chart, 'start');
  5080. }
  5081. }
  5082. function allPlugins(config) {
  5083. const localIds = {};
  5084. const plugins = [];
  5085. const keys = Object.keys(registry.plugins.items);
  5086. for(let i = 0; i < keys.length; i++){
  5087. plugins.push(registry.getPlugin(keys[i]));
  5088. }
  5089. const local = config.plugins || [];
  5090. for(let i = 0; i < local.length; i++){
  5091. const plugin = local[i];
  5092. if (plugins.indexOf(plugin) === -1) {
  5093. plugins.push(plugin);
  5094. localIds[plugin.id] = true;
  5095. }
  5096. }
  5097. return {
  5098. plugins,
  5099. localIds
  5100. };
  5101. }
  5102. function getOpts(options, all) {
  5103. if (!all && options === false) {
  5104. return null;
  5105. }
  5106. if (options === true) {
  5107. return {};
  5108. }
  5109. return options;
  5110. }
  5111. function createDescriptors(chart, { plugins , localIds }, options, all) {
  5112. const result = [];
  5113. const context = chart.getContext();
  5114. for (const plugin of plugins){
  5115. const id = plugin.id;
  5116. const opts = getOpts(options[id], all);
  5117. if (opts === null) {
  5118. continue;
  5119. }
  5120. result.push({
  5121. plugin,
  5122. options: pluginOpts(chart.config, {
  5123. plugin,
  5124. local: localIds[id]
  5125. }, opts, context)
  5126. });
  5127. }
  5128. return result;
  5129. }
  5130. function pluginOpts(config, { plugin , local }, opts, context) {
  5131. const keys = config.pluginScopeKeys(plugin);
  5132. const scopes = config.getOptionScopes(opts, keys);
  5133. if (local && plugin.defaults) {
  5134. scopes.push(plugin.defaults);
  5135. }
  5136. return config.createResolver(scopes, context, [
  5137. ''
  5138. ], {
  5139. scriptable: false,
  5140. indexable: false,
  5141. allKeys: true
  5142. });
  5143. }
  5144. function getIndexAxis(type, options) {
  5145. const datasetDefaults = helpers_segment.defaults.datasets[type] || {};
  5146. const datasetOptions = (options.datasets || {})[type] || {};
  5147. return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  5148. }
  5149. function getAxisFromDefaultScaleID(id, indexAxis) {
  5150. let axis = id;
  5151. if (id === '_index_') {
  5152. axis = indexAxis;
  5153. } else if (id === '_value_') {
  5154. axis = indexAxis === 'x' ? 'y' : 'x';
  5155. }
  5156. return axis;
  5157. }
  5158. function getDefaultScaleIDFromAxis(axis, indexAxis) {
  5159. return axis === indexAxis ? '_index_' : '_value_';
  5160. }
  5161. function idMatchesAxis(id) {
  5162. if (id === 'x' || id === 'y' || id === 'r') {
  5163. return id;
  5164. }
  5165. }
  5166. function axisFromPosition(position) {
  5167. if (position === 'top' || position === 'bottom') {
  5168. return 'x';
  5169. }
  5170. if (position === 'left' || position === 'right') {
  5171. return 'y';
  5172. }
  5173. }
  5174. function determineAxis(id, ...scaleOptions) {
  5175. if (idMatchesAxis(id)) {
  5176. return id;
  5177. }
  5178. for (const opts of scaleOptions){
  5179. const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
  5180. if (axis) {
  5181. return axis;
  5182. }
  5183. }
  5184. throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
  5185. }
  5186. function getAxisFromDataset(id, axis, dataset) {
  5187. if (dataset[axis + 'AxisID'] === id) {
  5188. return {
  5189. axis
  5190. };
  5191. }
  5192. }
  5193. function retrieveAxisFromDatasets(id, config) {
  5194. if (config.data && config.data.datasets) {
  5195. const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id);
  5196. if (boundDs.length) {
  5197. return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
  5198. }
  5199. }
  5200. return {};
  5201. }
  5202. function mergeScaleConfig(config, options) {
  5203. const chartDefaults = helpers_segment.overrides[config.type] || {
  5204. scales: {}
  5205. };
  5206. const configScales = options.scales || {};
  5207. const chartIndexAxis = getIndexAxis(config.type, options);
  5208. const scales = Object.create(null);
  5209. Object.keys(configScales).forEach((id)=>{
  5210. const scaleConf = configScales[id];
  5211. if (!helpers_segment.isObject(scaleConf)) {
  5212. return console.error(`Invalid scale configuration for scale: ${id}`);
  5213. }
  5214. if (scaleConf._proxy) {
  5215. return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
  5216. }
  5217. const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), helpers_segment.defaults.scales[scaleConf.type]);
  5218. const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
  5219. const defaultScaleOptions = chartDefaults.scales || {};
  5220. scales[id] = helpers_segment.mergeIf(Object.create(null), [
  5221. {
  5222. axis
  5223. },
  5224. scaleConf,
  5225. defaultScaleOptions[axis],
  5226. defaultScaleOptions[defaultId]
  5227. ]);
  5228. });
  5229. config.data.datasets.forEach((dataset)=>{
  5230. const type = dataset.type || config.type;
  5231. const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
  5232. const datasetDefaults = helpers_segment.overrides[type] || {};
  5233. const defaultScaleOptions = datasetDefaults.scales || {};
  5234. Object.keys(defaultScaleOptions).forEach((defaultID)=>{
  5235. const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
  5236. const id = dataset[axis + 'AxisID'] || axis;
  5237. scales[id] = scales[id] || Object.create(null);
  5238. helpers_segment.mergeIf(scales[id], [
  5239. {
  5240. axis
  5241. },
  5242. configScales[id],
  5243. defaultScaleOptions[defaultID]
  5244. ]);
  5245. });
  5246. });
  5247. Object.keys(scales).forEach((key)=>{
  5248. const scale = scales[key];
  5249. helpers_segment.mergeIf(scale, [
  5250. helpers_segment.defaults.scales[scale.type],
  5251. helpers_segment.defaults.scale
  5252. ]);
  5253. });
  5254. return scales;
  5255. }
  5256. function initOptions(config) {
  5257. const options = config.options || (config.options = {});
  5258. options.plugins = helpers_segment.valueOrDefault(options.plugins, {});
  5259. options.scales = mergeScaleConfig(config, options);
  5260. }
  5261. function initData(data) {
  5262. data = data || {};
  5263. data.datasets = data.datasets || [];
  5264. data.labels = data.labels || [];
  5265. return data;
  5266. }
  5267. function initConfig(config) {
  5268. config = config || {};
  5269. config.data = initData(config.data);
  5270. initOptions(config);
  5271. return config;
  5272. }
  5273. const keyCache = new Map();
  5274. const keysCached = new Set();
  5275. function cachedKeys(cacheKey, generate) {
  5276. let keys = keyCache.get(cacheKey);
  5277. if (!keys) {
  5278. keys = generate();
  5279. keyCache.set(cacheKey, keys);
  5280. keysCached.add(keys);
  5281. }
  5282. return keys;
  5283. }
  5284. const addIfFound = (set, obj, key)=>{
  5285. const opts = helpers_segment.resolveObjectKey(obj, key);
  5286. if (opts !== undefined) {
  5287. set.add(opts);
  5288. }
  5289. };
  5290. class Config {
  5291. constructor(config){
  5292. this._config = initConfig(config);
  5293. this._scopeCache = new Map();
  5294. this._resolverCache = new Map();
  5295. }
  5296. get platform() {
  5297. return this._config.platform;
  5298. }
  5299. get type() {
  5300. return this._config.type;
  5301. }
  5302. set type(type) {
  5303. this._config.type = type;
  5304. }
  5305. get data() {
  5306. return this._config.data;
  5307. }
  5308. set data(data) {
  5309. this._config.data = initData(data);
  5310. }
  5311. get options() {
  5312. return this._config.options;
  5313. }
  5314. set options(options) {
  5315. this._config.options = options;
  5316. }
  5317. get plugins() {
  5318. return this._config.plugins;
  5319. }
  5320. update() {
  5321. const config = this._config;
  5322. this.clearCache();
  5323. initOptions(config);
  5324. }
  5325. clearCache() {
  5326. this._scopeCache.clear();
  5327. this._resolverCache.clear();
  5328. }
  5329. datasetScopeKeys(datasetType) {
  5330. return cachedKeys(datasetType, ()=>[
  5331. [
  5332. `datasets.${datasetType}`,
  5333. ''
  5334. ]
  5335. ]);
  5336. }
  5337. datasetAnimationScopeKeys(datasetType, transition) {
  5338. return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
  5339. [
  5340. `datasets.${datasetType}.transitions.${transition}`,
  5341. `transitions.${transition}`
  5342. ],
  5343. [
  5344. `datasets.${datasetType}`,
  5345. ''
  5346. ]
  5347. ]);
  5348. }
  5349. datasetElementScopeKeys(datasetType, elementType) {
  5350. return cachedKeys(`${datasetType}-${elementType}`, ()=>[
  5351. [
  5352. `datasets.${datasetType}.elements.${elementType}`,
  5353. `datasets.${datasetType}`,
  5354. `elements.${elementType}`,
  5355. ''
  5356. ]
  5357. ]);
  5358. }
  5359. pluginScopeKeys(plugin) {
  5360. const id = plugin.id;
  5361. const type = this.type;
  5362. return cachedKeys(`${type}-plugin-${id}`, ()=>[
  5363. [
  5364. `plugins.${id}`,
  5365. ...plugin.additionalOptionScopes || []
  5366. ]
  5367. ]);
  5368. }
  5369. _cachedScopes(mainScope, resetCache) {
  5370. const _scopeCache = this._scopeCache;
  5371. let cache = _scopeCache.get(mainScope);
  5372. if (!cache || resetCache) {
  5373. cache = new Map();
  5374. _scopeCache.set(mainScope, cache);
  5375. }
  5376. return cache;
  5377. }
  5378. getOptionScopes(mainScope, keyLists, resetCache) {
  5379. const { options , type } = this;
  5380. const cache = this._cachedScopes(mainScope, resetCache);
  5381. const cached = cache.get(keyLists);
  5382. if (cached) {
  5383. return cached;
  5384. }
  5385. const scopes = new Set();
  5386. keyLists.forEach((keys)=>{
  5387. if (mainScope) {
  5388. scopes.add(mainScope);
  5389. keys.forEach((key)=>addIfFound(scopes, mainScope, key));
  5390. }
  5391. keys.forEach((key)=>addIfFound(scopes, options, key));
  5392. keys.forEach((key)=>addIfFound(scopes, helpers_segment.overrides[type] || {}, key));
  5393. keys.forEach((key)=>addIfFound(scopes, helpers_segment.defaults, key));
  5394. keys.forEach((key)=>addIfFound(scopes, helpers_segment.descriptors, key));
  5395. });
  5396. const array = Array.from(scopes);
  5397. if (array.length === 0) {
  5398. array.push(Object.create(null));
  5399. }
  5400. if (keysCached.has(keyLists)) {
  5401. cache.set(keyLists, array);
  5402. }
  5403. return array;
  5404. }
  5405. chartOptionScopes() {
  5406. const { options , type } = this;
  5407. return [
  5408. options,
  5409. helpers_segment.overrides[type] || {},
  5410. helpers_segment.defaults.datasets[type] || {},
  5411. {
  5412. type
  5413. },
  5414. helpers_segment.defaults,
  5415. helpers_segment.descriptors
  5416. ];
  5417. }
  5418. resolveNamedOptions(scopes, names, context, prefixes = [
  5419. ''
  5420. ]) {
  5421. const result = {
  5422. $shared: true
  5423. };
  5424. const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
  5425. let options = resolver;
  5426. if (needContext(resolver, names)) {
  5427. result.$shared = false;
  5428. context = helpers_segment.isFunction(context) ? context() : context;
  5429. const subResolver = this.createResolver(scopes, context, subPrefixes);
  5430. options = helpers_segment._attachContext(resolver, context, subResolver);
  5431. }
  5432. for (const prop of names){
  5433. result[prop] = options[prop];
  5434. }
  5435. return result;
  5436. }
  5437. createResolver(scopes, context, prefixes = [
  5438. ''
  5439. ], descriptorDefaults) {
  5440. const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
  5441. return helpers_segment.isObject(context) ? helpers_segment._attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
  5442. }
  5443. }
  5444. function getResolver(resolverCache, scopes, prefixes) {
  5445. let cache = resolverCache.get(scopes);
  5446. if (!cache) {
  5447. cache = new Map();
  5448. resolverCache.set(scopes, cache);
  5449. }
  5450. const cacheKey = prefixes.join();
  5451. let cached = cache.get(cacheKey);
  5452. if (!cached) {
  5453. const resolver = helpers_segment._createResolver(scopes, prefixes);
  5454. cached = {
  5455. resolver,
  5456. subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
  5457. };
  5458. cache.set(cacheKey, cached);
  5459. }
  5460. return cached;
  5461. }
  5462. const hasFunction = (value)=>helpers_segment.isObject(value) && Object.getOwnPropertyNames(value).reduce((acc, key)=>acc || helpers_segment.isFunction(value[key]), false);
  5463. function needContext(proxy, names) {
  5464. const { isScriptable , isIndexable } = helpers_segment._descriptors(proxy);
  5465. for (const prop of names){
  5466. const scriptable = isScriptable(prop);
  5467. const indexable = isIndexable(prop);
  5468. const value = (indexable || scriptable) && proxy[prop];
  5469. if (scriptable && (helpers_segment.isFunction(value) || hasFunction(value)) || indexable && helpers_segment.isArray(value)) {
  5470. return true;
  5471. }
  5472. }
  5473. return false;
  5474. }
  5475. var version = "4.4.0";
  5476. const KNOWN_POSITIONS = [
  5477. 'top',
  5478. 'bottom',
  5479. 'left',
  5480. 'right',
  5481. 'chartArea'
  5482. ];
  5483. function positionIsHorizontal(position, axis) {
  5484. return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
  5485. }
  5486. function compare2Level(l1, l2) {
  5487. return function(a, b) {
  5488. return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
  5489. };
  5490. }
  5491. function onAnimationsComplete(context) {
  5492. const chart = context.chart;
  5493. const animationOptions = chart.options.animation;
  5494. chart.notifyPlugins('afterRender');
  5495. helpers_segment.callback(animationOptions && animationOptions.onComplete, [
  5496. context
  5497. ], chart);
  5498. }
  5499. function onAnimationProgress(context) {
  5500. const chart = context.chart;
  5501. const animationOptions = chart.options.animation;
  5502. helpers_segment.callback(animationOptions && animationOptions.onProgress, [
  5503. context
  5504. ], chart);
  5505. }
  5506. function getCanvas(item) {
  5507. if (helpers_segment._isDomSupported() && typeof item === 'string') {
  5508. item = document.getElementById(item);
  5509. } else if (item && item.length) {
  5510. item = item[0];
  5511. }
  5512. if (item && item.canvas) {
  5513. item = item.canvas;
  5514. }
  5515. return item;
  5516. }
  5517. const instances = {};
  5518. const getChart = (key)=>{
  5519. const canvas = getCanvas(key);
  5520. return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
  5521. };
  5522. function moveNumericKeys(obj, start, move) {
  5523. const keys = Object.keys(obj);
  5524. for (const key of keys){
  5525. const intKey = +key;
  5526. if (intKey >= start) {
  5527. const value = obj[key];
  5528. delete obj[key];
  5529. if (move > 0 || intKey > start) {
  5530. obj[intKey + move] = value;
  5531. }
  5532. }
  5533. }
  5534. }
  5535. function determineLastEvent(e, lastEvent, inChartArea, isClick) {
  5536. if (!inChartArea || e.type === 'mouseout') {
  5537. return null;
  5538. }
  5539. if (isClick) {
  5540. return lastEvent;
  5541. }
  5542. return e;
  5543. }
  5544. function getSizeForArea(scale, chartArea, field) {
  5545. return scale.options.clip ? scale[field] : chartArea[field];
  5546. }
  5547. function getDatasetArea(meta, chartArea) {
  5548. const { xScale , yScale } = meta;
  5549. if (xScale && yScale) {
  5550. return {
  5551. left: getSizeForArea(xScale, chartArea, 'left'),
  5552. right: getSizeForArea(xScale, chartArea, 'right'),
  5553. top: getSizeForArea(yScale, chartArea, 'top'),
  5554. bottom: getSizeForArea(yScale, chartArea, 'bottom')
  5555. };
  5556. }
  5557. return chartArea;
  5558. }
  5559. class Chart {
  5560. static defaults = helpers_segment.defaults;
  5561. static instances = instances;
  5562. static overrides = helpers_segment.overrides;
  5563. static registry = registry;
  5564. static version = version;
  5565. static getChart = getChart;
  5566. static register(...items) {
  5567. registry.add(...items);
  5568. invalidatePlugins();
  5569. }
  5570. static unregister(...items) {
  5571. registry.remove(...items);
  5572. invalidatePlugins();
  5573. }
  5574. constructor(item, userConfig){
  5575. const config = this.config = new Config(userConfig);
  5576. const initialCanvas = getCanvas(item);
  5577. const existingChart = getChart(initialCanvas);
  5578. if (existingChart) {
  5579. 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.');
  5580. }
  5581. const options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5582. this.platform = new (config.platform || _detectPlatform(initialCanvas))();
  5583. this.platform.updateConfig(config);
  5584. const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
  5585. const canvas = context && context.canvas;
  5586. const height = canvas && canvas.height;
  5587. const width = canvas && canvas.width;
  5588. this.id = helpers_segment.uid();
  5589. this.ctx = context;
  5590. this.canvas = canvas;
  5591. this.width = width;
  5592. this.height = height;
  5593. this._options = options;
  5594. this._aspectRatio = this.aspectRatio;
  5595. this._layers = [];
  5596. this._metasets = [];
  5597. this._stacks = undefined;
  5598. this.boxes = [];
  5599. this.currentDevicePixelRatio = undefined;
  5600. this.chartArea = undefined;
  5601. this._active = [];
  5602. this._lastEvent = undefined;
  5603. this._listeners = {};
  5604. this._responsiveListeners = undefined;
  5605. this._sortedMetasets = [];
  5606. this.scales = {};
  5607. this._plugins = new PluginService();
  5608. this.$proxies = {};
  5609. this._hiddenIndices = {};
  5610. this.attached = false;
  5611. this._animationsDisabled = undefined;
  5612. this.$context = undefined;
  5613. this._doResize = helpers_segment.debounce((mode)=>this.update(mode), options.resizeDelay || 0);
  5614. this._dataChanges = [];
  5615. instances[this.id] = this;
  5616. if (!context || !canvas) {
  5617. console.error("Failed to create chart: can't acquire context from the given item");
  5618. return;
  5619. }
  5620. animator.listen(this, 'complete', onAnimationsComplete);
  5621. animator.listen(this, 'progress', onAnimationProgress);
  5622. this._initialize();
  5623. if (this.attached) {
  5624. this.update();
  5625. }
  5626. }
  5627. get aspectRatio() {
  5628. const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
  5629. if (!helpers_segment.isNullOrUndef(aspectRatio)) {
  5630. return aspectRatio;
  5631. }
  5632. if (maintainAspectRatio && _aspectRatio) {
  5633. return _aspectRatio;
  5634. }
  5635. return height ? width / height : null;
  5636. }
  5637. get data() {
  5638. return this.config.data;
  5639. }
  5640. set data(data) {
  5641. this.config.data = data;
  5642. }
  5643. get options() {
  5644. return this._options;
  5645. }
  5646. set options(options) {
  5647. this.config.options = options;
  5648. }
  5649. get registry() {
  5650. return registry;
  5651. }
  5652. _initialize() {
  5653. this.notifyPlugins('beforeInit');
  5654. if (this.options.responsive) {
  5655. this.resize();
  5656. } else {
  5657. helpers_segment.retinaScale(this, this.options.devicePixelRatio);
  5658. }
  5659. this.bindEvents();
  5660. this.notifyPlugins('afterInit');
  5661. return this;
  5662. }
  5663. clear() {
  5664. helpers_segment.clearCanvas(this.canvas, this.ctx);
  5665. return this;
  5666. }
  5667. stop() {
  5668. animator.stop(this);
  5669. return this;
  5670. }
  5671. resize(width, height) {
  5672. if (!animator.running(this)) {
  5673. this._resize(width, height);
  5674. } else {
  5675. this._resizeBeforeDraw = {
  5676. width,
  5677. height
  5678. };
  5679. }
  5680. }
  5681. _resize(width, height) {
  5682. const options = this.options;
  5683. const canvas = this.canvas;
  5684. const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
  5685. const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
  5686. const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
  5687. const mode = this.width ? 'resize' : 'attach';
  5688. this.width = newSize.width;
  5689. this.height = newSize.height;
  5690. this._aspectRatio = this.aspectRatio;
  5691. if (!helpers_segment.retinaScale(this, newRatio, true)) {
  5692. return;
  5693. }
  5694. this.notifyPlugins('resize', {
  5695. size: newSize
  5696. });
  5697. helpers_segment.callback(options.onResize, [
  5698. this,
  5699. newSize
  5700. ], this);
  5701. if (this.attached) {
  5702. if (this._doResize(mode)) {
  5703. this.render();
  5704. }
  5705. }
  5706. }
  5707. ensureScalesHaveIDs() {
  5708. const options = this.options;
  5709. const scalesOptions = options.scales || {};
  5710. helpers_segment.each(scalesOptions, (axisOptions, axisID)=>{
  5711. axisOptions.id = axisID;
  5712. });
  5713. }
  5714. buildOrUpdateScales() {
  5715. const options = this.options;
  5716. const scaleOpts = options.scales;
  5717. const scales = this.scales;
  5718. const updated = Object.keys(scales).reduce((obj, id)=>{
  5719. obj[id] = false;
  5720. return obj;
  5721. }, {});
  5722. let items = [];
  5723. if (scaleOpts) {
  5724. items = items.concat(Object.keys(scaleOpts).map((id)=>{
  5725. const scaleOptions = scaleOpts[id];
  5726. const axis = determineAxis(id, scaleOptions);
  5727. const isRadial = axis === 'r';
  5728. const isHorizontal = axis === 'x';
  5729. return {
  5730. options: scaleOptions,
  5731. dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
  5732. dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
  5733. };
  5734. }));
  5735. }
  5736. helpers_segment.each(items, (item)=>{
  5737. const scaleOptions = item.options;
  5738. const id = scaleOptions.id;
  5739. const axis = determineAxis(id, scaleOptions);
  5740. const scaleType = helpers_segment.valueOrDefault(scaleOptions.type, item.dtype);
  5741. if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
  5742. scaleOptions.position = item.dposition;
  5743. }
  5744. updated[id] = true;
  5745. let scale = null;
  5746. if (id in scales && scales[id].type === scaleType) {
  5747. scale = scales[id];
  5748. } else {
  5749. const scaleClass = registry.getScale(scaleType);
  5750. scale = new scaleClass({
  5751. id,
  5752. type: scaleType,
  5753. ctx: this.ctx,
  5754. chart: this
  5755. });
  5756. scales[scale.id] = scale;
  5757. }
  5758. scale.init(scaleOptions, options);
  5759. });
  5760. helpers_segment.each(updated, (hasUpdated, id)=>{
  5761. if (!hasUpdated) {
  5762. delete scales[id];
  5763. }
  5764. });
  5765. helpers_segment.each(scales, (scale)=>{
  5766. layouts.configure(this, scale, scale.options);
  5767. layouts.addBox(this, scale);
  5768. });
  5769. }
  5770. _updateMetasets() {
  5771. const metasets = this._metasets;
  5772. const numData = this.data.datasets.length;
  5773. const numMeta = metasets.length;
  5774. metasets.sort((a, b)=>a.index - b.index);
  5775. if (numMeta > numData) {
  5776. for(let i = numData; i < numMeta; ++i){
  5777. this._destroyDatasetMeta(i);
  5778. }
  5779. metasets.splice(numData, numMeta - numData);
  5780. }
  5781. this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
  5782. }
  5783. _removeUnreferencedMetasets() {
  5784. const { _metasets: metasets , data: { datasets } } = this;
  5785. if (metasets.length > datasets.length) {
  5786. delete this._stacks;
  5787. }
  5788. metasets.forEach((meta, index)=>{
  5789. if (datasets.filter((x)=>x === meta._dataset).length === 0) {
  5790. this._destroyDatasetMeta(index);
  5791. }
  5792. });
  5793. }
  5794. buildOrUpdateControllers() {
  5795. const newControllers = [];
  5796. const datasets = this.data.datasets;
  5797. let i, ilen;
  5798. this._removeUnreferencedMetasets();
  5799. for(i = 0, ilen = datasets.length; i < ilen; i++){
  5800. const dataset = datasets[i];
  5801. let meta = this.getDatasetMeta(i);
  5802. const type = dataset.type || this.config.type;
  5803. if (meta.type && meta.type !== type) {
  5804. this._destroyDatasetMeta(i);
  5805. meta = this.getDatasetMeta(i);
  5806. }
  5807. meta.type = type;
  5808. meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
  5809. meta.order = dataset.order || 0;
  5810. meta.index = i;
  5811. meta.label = '' + dataset.label;
  5812. meta.visible = this.isDatasetVisible(i);
  5813. if (meta.controller) {
  5814. meta.controller.updateIndex(i);
  5815. meta.controller.linkScales();
  5816. } else {
  5817. const ControllerClass = registry.getController(type);
  5818. const { datasetElementType , dataElementType } = helpers_segment.defaults.datasets[type];
  5819. Object.assign(ControllerClass, {
  5820. dataElementType: registry.getElement(dataElementType),
  5821. datasetElementType: datasetElementType && registry.getElement(datasetElementType)
  5822. });
  5823. meta.controller = new ControllerClass(this, i);
  5824. newControllers.push(meta.controller);
  5825. }
  5826. }
  5827. this._updateMetasets();
  5828. return newControllers;
  5829. }
  5830. _resetElements() {
  5831. helpers_segment.each(this.data.datasets, (dataset, datasetIndex)=>{
  5832. this.getDatasetMeta(datasetIndex).controller.reset();
  5833. }, this);
  5834. }
  5835. reset() {
  5836. this._resetElements();
  5837. this.notifyPlugins('reset');
  5838. }
  5839. update(mode) {
  5840. const config = this.config;
  5841. config.update();
  5842. const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
  5843. const animsDisabled = this._animationsDisabled = !options.animation;
  5844. this._updateScales();
  5845. this._checkEventBindings();
  5846. this._updateHiddenIndices();
  5847. this._plugins.invalidate();
  5848. if (this.notifyPlugins('beforeUpdate', {
  5849. mode,
  5850. cancelable: true
  5851. }) === false) {
  5852. return;
  5853. }
  5854. const newControllers = this.buildOrUpdateControllers();
  5855. this.notifyPlugins('beforeElementsUpdate');
  5856. let minPadding = 0;
  5857. for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
  5858. const { controller } = this.getDatasetMeta(i);
  5859. const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
  5860. controller.buildOrUpdateElements(reset);
  5861. minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
  5862. }
  5863. minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
  5864. this._updateLayout(minPadding);
  5865. if (!animsDisabled) {
  5866. helpers_segment.each(newControllers, (controller)=>{
  5867. controller.reset();
  5868. });
  5869. }
  5870. this._updateDatasets(mode);
  5871. this.notifyPlugins('afterUpdate', {
  5872. mode
  5873. });
  5874. this._layers.sort(compare2Level('z', '_idx'));
  5875. const { _active , _lastEvent } = this;
  5876. if (_lastEvent) {
  5877. this._eventHandler(_lastEvent, true);
  5878. } else if (_active.length) {
  5879. this._updateHoverStyles(_active, _active, true);
  5880. }
  5881. this.render();
  5882. }
  5883. _updateScales() {
  5884. helpers_segment.each(this.scales, (scale)=>{
  5885. layouts.removeBox(this, scale);
  5886. });
  5887. this.ensureScalesHaveIDs();
  5888. this.buildOrUpdateScales();
  5889. }
  5890. _checkEventBindings() {
  5891. const options = this.options;
  5892. const existingEvents = new Set(Object.keys(this._listeners));
  5893. const newEvents = new Set(options.events);
  5894. if (!helpers_segment.setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
  5895. this.unbindEvents();
  5896. this.bindEvents();
  5897. }
  5898. }
  5899. _updateHiddenIndices() {
  5900. const { _hiddenIndices } = this;
  5901. const changes = this._getUniformDataChanges() || [];
  5902. for (const { method , start , count } of changes){
  5903. const move = method === '_removeElements' ? -count : count;
  5904. moveNumericKeys(_hiddenIndices, start, move);
  5905. }
  5906. }
  5907. _getUniformDataChanges() {
  5908. const _dataChanges = this._dataChanges;
  5909. if (!_dataChanges || !_dataChanges.length) {
  5910. return;
  5911. }
  5912. this._dataChanges = [];
  5913. const datasetCount = this.data.datasets.length;
  5914. const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
  5915. const changeSet = makeSet(0);
  5916. for(let i = 1; i < datasetCount; i++){
  5917. if (!helpers_segment.setsEqual(changeSet, makeSet(i))) {
  5918. return;
  5919. }
  5920. }
  5921. return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
  5922. method: a[1],
  5923. start: +a[2],
  5924. count: +a[3]
  5925. }));
  5926. }
  5927. _updateLayout(minPadding) {
  5928. if (this.notifyPlugins('beforeLayout', {
  5929. cancelable: true
  5930. }) === false) {
  5931. return;
  5932. }
  5933. layouts.update(this, this.width, this.height, minPadding);
  5934. const area = this.chartArea;
  5935. const noArea = area.width <= 0 || area.height <= 0;
  5936. this._layers = [];
  5937. helpers_segment.each(this.boxes, (box)=>{
  5938. if (noArea && box.position === 'chartArea') {
  5939. return;
  5940. }
  5941. if (box.configure) {
  5942. box.configure();
  5943. }
  5944. this._layers.push(...box._layers());
  5945. }, this);
  5946. this._layers.forEach((item, index)=>{
  5947. item._idx = index;
  5948. });
  5949. this.notifyPlugins('afterLayout');
  5950. }
  5951. _updateDatasets(mode) {
  5952. if (this.notifyPlugins('beforeDatasetsUpdate', {
  5953. mode,
  5954. cancelable: true
  5955. }) === false) {
  5956. return;
  5957. }
  5958. for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  5959. this.getDatasetMeta(i).controller.configure();
  5960. }
  5961. for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  5962. this._updateDataset(i, helpers_segment.isFunction(mode) ? mode({
  5963. datasetIndex: i
  5964. }) : mode);
  5965. }
  5966. this.notifyPlugins('afterDatasetsUpdate', {
  5967. mode
  5968. });
  5969. }
  5970. _updateDataset(index, mode) {
  5971. const meta = this.getDatasetMeta(index);
  5972. const args = {
  5973. meta,
  5974. index,
  5975. mode,
  5976. cancelable: true
  5977. };
  5978. if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
  5979. return;
  5980. }
  5981. meta.controller._update(mode);
  5982. args.cancelable = false;
  5983. this.notifyPlugins('afterDatasetUpdate', args);
  5984. }
  5985. render() {
  5986. if (this.notifyPlugins('beforeRender', {
  5987. cancelable: true
  5988. }) === false) {
  5989. return;
  5990. }
  5991. if (animator.has(this)) {
  5992. if (this.attached && !animator.running(this)) {
  5993. animator.start(this);
  5994. }
  5995. } else {
  5996. this.draw();
  5997. onAnimationsComplete({
  5998. chart: this
  5999. });
  6000. }
  6001. }
  6002. draw() {
  6003. let i;
  6004. if (this._resizeBeforeDraw) {
  6005. const { width , height } = this._resizeBeforeDraw;
  6006. this._resize(width, height);
  6007. this._resizeBeforeDraw = null;
  6008. }
  6009. this.clear();
  6010. if (this.width <= 0 || this.height <= 0) {
  6011. return;
  6012. }
  6013. if (this.notifyPlugins('beforeDraw', {
  6014. cancelable: true
  6015. }) === false) {
  6016. return;
  6017. }
  6018. const layers = this._layers;
  6019. for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
  6020. layers[i].draw(this.chartArea);
  6021. }
  6022. this._drawDatasets();
  6023. for(; i < layers.length; ++i){
  6024. layers[i].draw(this.chartArea);
  6025. }
  6026. this.notifyPlugins('afterDraw');
  6027. }
  6028. _getSortedDatasetMetas(filterVisible) {
  6029. const metasets = this._sortedMetasets;
  6030. const result = [];
  6031. let i, ilen;
  6032. for(i = 0, ilen = metasets.length; i < ilen; ++i){
  6033. const meta = metasets[i];
  6034. if (!filterVisible || meta.visible) {
  6035. result.push(meta);
  6036. }
  6037. }
  6038. return result;
  6039. }
  6040. getSortedVisibleDatasetMetas() {
  6041. return this._getSortedDatasetMetas(true);
  6042. }
  6043. _drawDatasets() {
  6044. if (this.notifyPlugins('beforeDatasetsDraw', {
  6045. cancelable: true
  6046. }) === false) {
  6047. return;
  6048. }
  6049. const metasets = this.getSortedVisibleDatasetMetas();
  6050. for(let i = metasets.length - 1; i >= 0; --i){
  6051. this._drawDataset(metasets[i]);
  6052. }
  6053. this.notifyPlugins('afterDatasetsDraw');
  6054. }
  6055. _drawDataset(meta) {
  6056. const ctx = this.ctx;
  6057. const clip = meta._clip;
  6058. const useClip = !clip.disabled;
  6059. const area = getDatasetArea(meta, this.chartArea);
  6060. const args = {
  6061. meta,
  6062. index: meta.index,
  6063. cancelable: true
  6064. };
  6065. if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
  6066. return;
  6067. }
  6068. if (useClip) {
  6069. helpers_segment.clipArea(ctx, {
  6070. left: clip.left === false ? 0 : area.left - clip.left,
  6071. right: clip.right === false ? this.width : area.right + clip.right,
  6072. top: clip.top === false ? 0 : area.top - clip.top,
  6073. bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
  6074. });
  6075. }
  6076. meta.controller.draw();
  6077. if (useClip) {
  6078. helpers_segment.unclipArea(ctx);
  6079. }
  6080. args.cancelable = false;
  6081. this.notifyPlugins('afterDatasetDraw', args);
  6082. }
  6083. isPointInArea(point) {
  6084. return helpers_segment._isPointInArea(point, this.chartArea, this._minPadding);
  6085. }
  6086. getElementsAtEventForMode(e, mode, options, useFinalPosition) {
  6087. const method = Interaction.modes[mode];
  6088. if (typeof method === 'function') {
  6089. return method(this, e, options, useFinalPosition);
  6090. }
  6091. return [];
  6092. }
  6093. getDatasetMeta(datasetIndex) {
  6094. const dataset = this.data.datasets[datasetIndex];
  6095. const metasets = this._metasets;
  6096. let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
  6097. if (!meta) {
  6098. meta = {
  6099. type: null,
  6100. data: [],
  6101. dataset: null,
  6102. controller: null,
  6103. hidden: null,
  6104. xAxisID: null,
  6105. yAxisID: null,
  6106. order: dataset && dataset.order || 0,
  6107. index: datasetIndex,
  6108. _dataset: dataset,
  6109. _parsed: [],
  6110. _sorted: false
  6111. };
  6112. metasets.push(meta);
  6113. }
  6114. return meta;
  6115. }
  6116. getContext() {
  6117. return this.$context || (this.$context = helpers_segment.createContext(null, {
  6118. chart: this,
  6119. type: 'chart'
  6120. }));
  6121. }
  6122. getVisibleDatasetCount() {
  6123. return this.getSortedVisibleDatasetMetas().length;
  6124. }
  6125. isDatasetVisible(datasetIndex) {
  6126. const dataset = this.data.datasets[datasetIndex];
  6127. if (!dataset) {
  6128. return false;
  6129. }
  6130. const meta = this.getDatasetMeta(datasetIndex);
  6131. return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
  6132. }
  6133. setDatasetVisibility(datasetIndex, visible) {
  6134. const meta = this.getDatasetMeta(datasetIndex);
  6135. meta.hidden = !visible;
  6136. }
  6137. toggleDataVisibility(index) {
  6138. this._hiddenIndices[index] = !this._hiddenIndices[index];
  6139. }
  6140. getDataVisibility(index) {
  6141. return !this._hiddenIndices[index];
  6142. }
  6143. _updateVisibility(datasetIndex, dataIndex, visible) {
  6144. const mode = visible ? 'show' : 'hide';
  6145. const meta = this.getDatasetMeta(datasetIndex);
  6146. const anims = meta.controller._resolveAnimations(undefined, mode);
  6147. if (helpers_segment.defined(dataIndex)) {
  6148. meta.data[dataIndex].hidden = !visible;
  6149. this.update();
  6150. } else {
  6151. this.setDatasetVisibility(datasetIndex, visible);
  6152. anims.update(meta, {
  6153. visible
  6154. });
  6155. this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
  6156. }
  6157. }
  6158. hide(datasetIndex, dataIndex) {
  6159. this._updateVisibility(datasetIndex, dataIndex, false);
  6160. }
  6161. show(datasetIndex, dataIndex) {
  6162. this._updateVisibility(datasetIndex, dataIndex, true);
  6163. }
  6164. _destroyDatasetMeta(datasetIndex) {
  6165. const meta = this._metasets[datasetIndex];
  6166. if (meta && meta.controller) {
  6167. meta.controller._destroy();
  6168. }
  6169. delete this._metasets[datasetIndex];
  6170. }
  6171. _stop() {
  6172. let i, ilen;
  6173. this.stop();
  6174. animator.remove(this);
  6175. for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
  6176. this._destroyDatasetMeta(i);
  6177. }
  6178. }
  6179. destroy() {
  6180. this.notifyPlugins('beforeDestroy');
  6181. const { canvas , ctx } = this;
  6182. this._stop();
  6183. this.config.clearCache();
  6184. if (canvas) {
  6185. this.unbindEvents();
  6186. helpers_segment.clearCanvas(canvas, ctx);
  6187. this.platform.releaseContext(ctx);
  6188. this.canvas = null;
  6189. this.ctx = null;
  6190. }
  6191. delete instances[this.id];
  6192. this.notifyPlugins('afterDestroy');
  6193. }
  6194. toBase64Image(...args) {
  6195. return this.canvas.toDataURL(...args);
  6196. }
  6197. bindEvents() {
  6198. this.bindUserEvents();
  6199. if (this.options.responsive) {
  6200. this.bindResponsiveEvents();
  6201. } else {
  6202. this.attached = true;
  6203. }
  6204. }
  6205. bindUserEvents() {
  6206. const listeners = this._listeners;
  6207. const platform = this.platform;
  6208. const _add = (type, listener)=>{
  6209. platform.addEventListener(this, type, listener);
  6210. listeners[type] = listener;
  6211. };
  6212. const listener = (e, x, y)=>{
  6213. e.offsetX = x;
  6214. e.offsetY = y;
  6215. this._eventHandler(e);
  6216. };
  6217. helpers_segment.each(this.options.events, (type)=>_add(type, listener));
  6218. }
  6219. bindResponsiveEvents() {
  6220. if (!this._responsiveListeners) {
  6221. this._responsiveListeners = {};
  6222. }
  6223. const listeners = this._responsiveListeners;
  6224. const platform = this.platform;
  6225. const _add = (type, listener)=>{
  6226. platform.addEventListener(this, type, listener);
  6227. listeners[type] = listener;
  6228. };
  6229. const _remove = (type, listener)=>{
  6230. if (listeners[type]) {
  6231. platform.removeEventListener(this, type, listener);
  6232. delete listeners[type];
  6233. }
  6234. };
  6235. const listener = (width, height)=>{
  6236. if (this.canvas) {
  6237. this.resize(width, height);
  6238. }
  6239. };
  6240. let detached;
  6241. const attached = ()=>{
  6242. _remove('attach', attached);
  6243. this.attached = true;
  6244. this.resize();
  6245. _add('resize', listener);
  6246. _add('detach', detached);
  6247. };
  6248. detached = ()=>{
  6249. this.attached = false;
  6250. _remove('resize', listener);
  6251. this._stop();
  6252. this._resize(0, 0);
  6253. _add('attach', attached);
  6254. };
  6255. if (platform.isAttached(this.canvas)) {
  6256. attached();
  6257. } else {
  6258. detached();
  6259. }
  6260. }
  6261. unbindEvents() {
  6262. helpers_segment.each(this._listeners, (listener, type)=>{
  6263. this.platform.removeEventListener(this, type, listener);
  6264. });
  6265. this._listeners = {};
  6266. helpers_segment.each(this._responsiveListeners, (listener, type)=>{
  6267. this.platform.removeEventListener(this, type, listener);
  6268. });
  6269. this._responsiveListeners = undefined;
  6270. }
  6271. updateHoverStyle(items, mode, enabled) {
  6272. const prefix = enabled ? 'set' : 'remove';
  6273. let meta, item, i, ilen;
  6274. if (mode === 'dataset') {
  6275. meta = this.getDatasetMeta(items[0].datasetIndex);
  6276. meta.controller['_' + prefix + 'DatasetHoverStyle']();
  6277. }
  6278. for(i = 0, ilen = items.length; i < ilen; ++i){
  6279. item = items[i];
  6280. const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
  6281. if (controller) {
  6282. controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
  6283. }
  6284. }
  6285. }
  6286. getActiveElements() {
  6287. return this._active || [];
  6288. }
  6289. setActiveElements(activeElements) {
  6290. const lastActive = this._active || [];
  6291. const active = activeElements.map(({ datasetIndex , index })=>{
  6292. const meta = this.getDatasetMeta(datasetIndex);
  6293. if (!meta) {
  6294. throw new Error('No dataset found at index ' + datasetIndex);
  6295. }
  6296. return {
  6297. datasetIndex,
  6298. element: meta.data[index],
  6299. index
  6300. };
  6301. });
  6302. const changed = !helpers_segment._elementsEqual(active, lastActive);
  6303. if (changed) {
  6304. this._active = active;
  6305. this._lastEvent = null;
  6306. this._updateHoverStyles(active, lastActive);
  6307. }
  6308. }
  6309. notifyPlugins(hook, args, filter) {
  6310. return this._plugins.notify(this, hook, args, filter);
  6311. }
  6312. isPluginEnabled(pluginId) {
  6313. return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
  6314. }
  6315. _updateHoverStyles(active, lastActive, replay) {
  6316. const hoverOptions = this.options.hover;
  6317. const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
  6318. const deactivated = diff(lastActive, active);
  6319. const activated = replay ? active : diff(active, lastActive);
  6320. if (deactivated.length) {
  6321. this.updateHoverStyle(deactivated, hoverOptions.mode, false);
  6322. }
  6323. if (activated.length && hoverOptions.mode) {
  6324. this.updateHoverStyle(activated, hoverOptions.mode, true);
  6325. }
  6326. }
  6327. _eventHandler(e, replay) {
  6328. const args = {
  6329. event: e,
  6330. replay,
  6331. cancelable: true,
  6332. inChartArea: this.isPointInArea(e)
  6333. };
  6334. const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
  6335. if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
  6336. return;
  6337. }
  6338. const changed = this._handleEvent(e, replay, args.inChartArea);
  6339. args.cancelable = false;
  6340. this.notifyPlugins('afterEvent', args, eventFilter);
  6341. if (changed || args.changed) {
  6342. this.render();
  6343. }
  6344. return this;
  6345. }
  6346. _handleEvent(e, replay, inChartArea) {
  6347. const { _active: lastActive = [] , options } = this;
  6348. const useFinalPosition = replay;
  6349. const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
  6350. const isClick = helpers_segment._isClickEvent(e);
  6351. const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
  6352. if (inChartArea) {
  6353. this._lastEvent = null;
  6354. helpers_segment.callback(options.onHover, [
  6355. e,
  6356. active,
  6357. this
  6358. ], this);
  6359. if (isClick) {
  6360. helpers_segment.callback(options.onClick, [
  6361. e,
  6362. active,
  6363. this
  6364. ], this);
  6365. }
  6366. }
  6367. const changed = !helpers_segment._elementsEqual(active, lastActive);
  6368. if (changed || replay) {
  6369. this._active = active;
  6370. this._updateHoverStyles(active, lastActive, replay);
  6371. }
  6372. this._lastEvent = lastEvent;
  6373. return changed;
  6374. }
  6375. _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
  6376. if (e.type === 'mouseout') {
  6377. return [];
  6378. }
  6379. if (!inChartArea) {
  6380. return lastActive;
  6381. }
  6382. const hoverOptions = this.options.hover;
  6383. return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
  6384. }
  6385. }
  6386. function invalidatePlugins() {
  6387. return helpers_segment.each(Chart.instances, (chart)=>chart._plugins.invalidate());
  6388. }
  6389. function clipArc(ctx, element, endAngle) {
  6390. const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
  6391. let angleMargin = pixelMargin / outerRadius;
  6392. // Draw an inner border by clipping the arc and drawing a double-width border
  6393. // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
  6394. ctx.beginPath();
  6395. ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
  6396. if (innerRadius > pixelMargin) {
  6397. angleMargin = pixelMargin / innerRadius;
  6398. ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
  6399. } else {
  6400. ctx.arc(x, y, pixelMargin, endAngle + helpers_segment.HALF_PI, startAngle - helpers_segment.HALF_PI);
  6401. }
  6402. ctx.closePath();
  6403. ctx.clip();
  6404. }
  6405. function toRadiusCorners(value) {
  6406. return helpers_segment._readValueToProps(value, [
  6407. 'outerStart',
  6408. 'outerEnd',
  6409. 'innerStart',
  6410. 'innerEnd'
  6411. ]);
  6412. }
  6413. /**
  6414. * Parse border radius from the provided options
  6415. */ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
  6416. const o = toRadiusCorners(arc.options.borderRadius);
  6417. const halfThickness = (outerRadius - innerRadius) / 2;
  6418. const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
  6419. // Outer limits are complicated. We want to compute the available angular distance at
  6420. // a radius of outerRadius - borderRadius because for small angular distances, this term limits.
  6421. // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
  6422. //
  6423. // If the borderRadius is large, that value can become negative.
  6424. // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
  6425. // we know that the thickness term will dominate and compute the limits at that point
  6426. const computeOuterLimit = (val)=>{
  6427. const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
  6428. return helpers_segment._limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
  6429. };
  6430. return {
  6431. outerStart: computeOuterLimit(o.outerStart),
  6432. outerEnd: computeOuterLimit(o.outerEnd),
  6433. innerStart: helpers_segment._limitValue(o.innerStart, 0, innerLimit),
  6434. innerEnd: helpers_segment._limitValue(o.innerEnd, 0, innerLimit)
  6435. };
  6436. }
  6437. /**
  6438. * Convert (r, 𝜃) to (x, y)
  6439. */ function rThetaToXY(r, theta, x, y) {
  6440. return {
  6441. x: x + r * Math.cos(theta),
  6442. y: y + r * Math.sin(theta)
  6443. };
  6444. }
  6445. /**
  6446. * Path the arc, respecting border radius by separating into left and right halves.
  6447. *
  6448. * Start End
  6449. *
  6450. * 1--->a--->2 Outer
  6451. * / \
  6452. * 8 3
  6453. * | |
  6454. * | |
  6455. * 7 4
  6456. * \ /
  6457. * 6<---b<---5 Inner
  6458. */ function pathArc(ctx, element, offset, spacing, end, circular) {
  6459. const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
  6460. const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
  6461. const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
  6462. let spacingOffset = 0;
  6463. const alpha = end - start;
  6464. if (spacing) {
  6465. // When spacing is present, it is the same for all items
  6466. // So we adjust the start and end angle of the arc such that
  6467. // the distance is the same as it would be without the spacing
  6468. const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
  6469. const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
  6470. const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
  6471. const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
  6472. spacingOffset = (alpha - adjustedAngle) / 2;
  6473. }
  6474. const beta = Math.max(0.001, alpha * outerRadius - offset / helpers_segment.PI) / outerRadius;
  6475. const angleOffset = (alpha - beta) / 2;
  6476. const startAngle = start + angleOffset + spacingOffset;
  6477. const endAngle = end - angleOffset - spacingOffset;
  6478. const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
  6479. const outerStartAdjustedRadius = outerRadius - outerStart;
  6480. const outerEndAdjustedRadius = outerRadius - outerEnd;
  6481. const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
  6482. const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
  6483. const innerStartAdjustedRadius = innerRadius + innerStart;
  6484. const innerEndAdjustedRadius = innerRadius + innerEnd;
  6485. const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
  6486. const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
  6487. ctx.beginPath();
  6488. if (circular) {
  6489. // The first arc segments from point 1 to point a to point 2
  6490. const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
  6491. ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
  6492. ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
  6493. // The corner segment from point 2 to point 3
  6494. if (outerEnd > 0) {
  6495. const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
  6496. ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + helpers_segment.HALF_PI);
  6497. }
  6498. // The line from point 3 to point 4
  6499. const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
  6500. ctx.lineTo(p4.x, p4.y);
  6501. // The corner segment from point 4 to point 5
  6502. if (innerEnd > 0) {
  6503. const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
  6504. ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + helpers_segment.HALF_PI, innerEndAdjustedAngle + Math.PI);
  6505. }
  6506. // The inner arc from point 5 to point b to point 6
  6507. const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
  6508. ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
  6509. ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
  6510. // The corner segment from point 6 to point 7
  6511. if (innerStart > 0) {
  6512. const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
  6513. ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - helpers_segment.HALF_PI);
  6514. }
  6515. // The line from point 7 to point 8
  6516. const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
  6517. ctx.lineTo(p8.x, p8.y);
  6518. // The corner segment from point 8 to point 1
  6519. if (outerStart > 0) {
  6520. const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
  6521. ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - helpers_segment.HALF_PI, outerStartAdjustedAngle);
  6522. }
  6523. } else {
  6524. ctx.moveTo(x, y);
  6525. const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
  6526. const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
  6527. ctx.lineTo(outerStartX, outerStartY);
  6528. const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
  6529. const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
  6530. ctx.lineTo(outerEndX, outerEndY);
  6531. }
  6532. ctx.closePath();
  6533. }
  6534. function drawArc(ctx, element, offset, spacing, circular) {
  6535. const { fullCircles , startAngle , circumference } = element;
  6536. let endAngle = element.endAngle;
  6537. if (fullCircles) {
  6538. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6539. for(let i = 0; i < fullCircles; ++i){
  6540. ctx.fill();
  6541. }
  6542. if (!isNaN(circumference)) {
  6543. endAngle = startAngle + (circumference % helpers_segment.TAU || helpers_segment.TAU);
  6544. }
  6545. }
  6546. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6547. ctx.fill();
  6548. return endAngle;
  6549. }
  6550. function drawBorder(ctx, element, offset, spacing, circular) {
  6551. const { fullCircles , startAngle , circumference , options } = element;
  6552. const { borderWidth , borderJoinStyle , borderDash , borderDashOffset } = options;
  6553. const inner = options.borderAlign === 'inner';
  6554. if (!borderWidth) {
  6555. return;
  6556. }
  6557. ctx.setLineDash(borderDash || []);
  6558. ctx.lineDashOffset = borderDashOffset;
  6559. if (inner) {
  6560. ctx.lineWidth = borderWidth * 2;
  6561. ctx.lineJoin = borderJoinStyle || 'round';
  6562. } else {
  6563. ctx.lineWidth = borderWidth;
  6564. ctx.lineJoin = borderJoinStyle || 'bevel';
  6565. }
  6566. let endAngle = element.endAngle;
  6567. if (fullCircles) {
  6568. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6569. for(let i = 0; i < fullCircles; ++i){
  6570. ctx.stroke();
  6571. }
  6572. if (!isNaN(circumference)) {
  6573. endAngle = startAngle + (circumference % helpers_segment.TAU || helpers_segment.TAU);
  6574. }
  6575. }
  6576. if (inner) {
  6577. clipArc(ctx, element, endAngle);
  6578. }
  6579. if (!fullCircles) {
  6580. pathArc(ctx, element, offset, spacing, endAngle, circular);
  6581. ctx.stroke();
  6582. }
  6583. }
  6584. class ArcElement extends Element {
  6585. static id = 'arc';
  6586. static defaults = {
  6587. borderAlign: 'center',
  6588. borderColor: '#fff',
  6589. borderDash: [],
  6590. borderDashOffset: 0,
  6591. borderJoinStyle: undefined,
  6592. borderRadius: 0,
  6593. borderWidth: 2,
  6594. offset: 0,
  6595. spacing: 0,
  6596. angle: undefined,
  6597. circular: true
  6598. };
  6599. static defaultRoutes = {
  6600. backgroundColor: 'backgroundColor'
  6601. };
  6602. static descriptors = {
  6603. _scriptable: true,
  6604. _indexable: (name)=>name !== 'borderDash'
  6605. };
  6606. circumference;
  6607. endAngle;
  6608. fullCircles;
  6609. innerRadius;
  6610. outerRadius;
  6611. pixelMargin;
  6612. startAngle;
  6613. constructor(cfg){
  6614. super();
  6615. this.options = undefined;
  6616. this.circumference = undefined;
  6617. this.startAngle = undefined;
  6618. this.endAngle = undefined;
  6619. this.innerRadius = undefined;
  6620. this.outerRadius = undefined;
  6621. this.pixelMargin = 0;
  6622. this.fullCircles = 0;
  6623. if (cfg) {
  6624. Object.assign(this, cfg);
  6625. }
  6626. }
  6627. inRange(chartX, chartY, useFinalPosition) {
  6628. const point = this.getProps([
  6629. 'x',
  6630. 'y'
  6631. ], useFinalPosition);
  6632. const { angle , distance } = helpers_segment.getAngleFromPoint(point, {
  6633. x: chartX,
  6634. y: chartY
  6635. });
  6636. const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
  6637. 'startAngle',
  6638. 'endAngle',
  6639. 'innerRadius',
  6640. 'outerRadius',
  6641. 'circumference'
  6642. ], useFinalPosition);
  6643. const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
  6644. const _circumference = helpers_segment.valueOrDefault(circumference, endAngle - startAngle);
  6645. const betweenAngles = _circumference >= helpers_segment.TAU || helpers_segment._angleBetween(angle, startAngle, endAngle);
  6646. const withinRadius = helpers_segment._isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
  6647. return betweenAngles && withinRadius;
  6648. }
  6649. getCenterPoint(useFinalPosition) {
  6650. const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
  6651. 'x',
  6652. 'y',
  6653. 'startAngle',
  6654. 'endAngle',
  6655. 'innerRadius',
  6656. 'outerRadius'
  6657. ], useFinalPosition);
  6658. const { offset , spacing } = this.options;
  6659. const halfAngle = (startAngle + endAngle) / 2;
  6660. const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
  6661. return {
  6662. x: x + Math.cos(halfAngle) * halfRadius,
  6663. y: y + Math.sin(halfAngle) * halfRadius
  6664. };
  6665. }
  6666. tooltipPosition(useFinalPosition) {
  6667. return this.getCenterPoint(useFinalPosition);
  6668. }
  6669. draw(ctx) {
  6670. const { options , circumference } = this;
  6671. const offset = (options.offset || 0) / 4;
  6672. const spacing = (options.spacing || 0) / 2;
  6673. const circular = options.circular;
  6674. this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
  6675. this.fullCircles = circumference > helpers_segment.TAU ? Math.floor(circumference / helpers_segment.TAU) : 0;
  6676. if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
  6677. return;
  6678. }
  6679. ctx.save();
  6680. const halfAngle = (this.startAngle + this.endAngle) / 2;
  6681. ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
  6682. const fix = 1 - Math.sin(Math.min(helpers_segment.PI, circumference || 0));
  6683. const radiusOffset = offset * fix;
  6684. ctx.fillStyle = options.backgroundColor;
  6685. ctx.strokeStyle = options.borderColor;
  6686. drawArc(ctx, this, radiusOffset, spacing, circular);
  6687. drawBorder(ctx, this, radiusOffset, spacing, circular);
  6688. ctx.restore();
  6689. }
  6690. }
  6691. function setStyle(ctx, options, style = options) {
  6692. ctx.lineCap = helpers_segment.valueOrDefault(style.borderCapStyle, options.borderCapStyle);
  6693. ctx.setLineDash(helpers_segment.valueOrDefault(style.borderDash, options.borderDash));
  6694. ctx.lineDashOffset = helpers_segment.valueOrDefault(style.borderDashOffset, options.borderDashOffset);
  6695. ctx.lineJoin = helpers_segment.valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
  6696. ctx.lineWidth = helpers_segment.valueOrDefault(style.borderWidth, options.borderWidth);
  6697. ctx.strokeStyle = helpers_segment.valueOrDefault(style.borderColor, options.borderColor);
  6698. }
  6699. function lineTo(ctx, previous, target) {
  6700. ctx.lineTo(target.x, target.y);
  6701. }
  6702. function getLineMethod(options) {
  6703. if (options.stepped) {
  6704. return helpers_segment._steppedLineTo;
  6705. }
  6706. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6707. return helpers_segment._bezierCurveTo;
  6708. }
  6709. return lineTo;
  6710. }
  6711. function pathVars(points, segment, params = {}) {
  6712. const count = points.length;
  6713. const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
  6714. const { start: segmentStart , end: segmentEnd } = segment;
  6715. const start = Math.max(paramsStart, segmentStart);
  6716. const end = Math.min(paramsEnd, segmentEnd);
  6717. const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
  6718. return {
  6719. count,
  6720. start,
  6721. loop: segment.loop,
  6722. ilen: end < start && !outside ? count + end - start : end - start
  6723. };
  6724. }
  6725. function pathSegment(ctx, line, segment, params) {
  6726. const { points , options } = line;
  6727. const { count , start , loop , ilen } = pathVars(points, segment, params);
  6728. const lineMethod = getLineMethod(options);
  6729. let { move =true , reverse } = params || {};
  6730. let i, point, prev;
  6731. for(i = 0; i <= ilen; ++i){
  6732. point = points[(start + (reverse ? ilen - i : i)) % count];
  6733. if (point.skip) {
  6734. continue;
  6735. } else if (move) {
  6736. ctx.moveTo(point.x, point.y);
  6737. move = false;
  6738. } else {
  6739. lineMethod(ctx, prev, point, reverse, options.stepped);
  6740. }
  6741. prev = point;
  6742. }
  6743. if (loop) {
  6744. point = points[(start + (reverse ? ilen : 0)) % count];
  6745. lineMethod(ctx, prev, point, reverse, options.stepped);
  6746. }
  6747. return !!loop;
  6748. }
  6749. function fastPathSegment(ctx, line, segment, params) {
  6750. const points = line.points;
  6751. const { count , start , ilen } = pathVars(points, segment, params);
  6752. const { move =true , reverse } = params || {};
  6753. let avgX = 0;
  6754. let countX = 0;
  6755. let i, point, prevX, minY, maxY, lastY;
  6756. const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
  6757. const drawX = ()=>{
  6758. if (minY !== maxY) {
  6759. ctx.lineTo(avgX, maxY);
  6760. ctx.lineTo(avgX, minY);
  6761. ctx.lineTo(avgX, lastY);
  6762. }
  6763. };
  6764. if (move) {
  6765. point = points[pointIndex(0)];
  6766. ctx.moveTo(point.x, point.y);
  6767. }
  6768. for(i = 0; i <= ilen; ++i){
  6769. point = points[pointIndex(i)];
  6770. if (point.skip) {
  6771. continue;
  6772. }
  6773. const x = point.x;
  6774. const y = point.y;
  6775. const truncX = x | 0;
  6776. if (truncX === prevX) {
  6777. if (y < minY) {
  6778. minY = y;
  6779. } else if (y > maxY) {
  6780. maxY = y;
  6781. }
  6782. avgX = (countX * avgX + x) / ++countX;
  6783. } else {
  6784. drawX();
  6785. ctx.lineTo(x, y);
  6786. prevX = truncX;
  6787. countX = 0;
  6788. minY = maxY = y;
  6789. }
  6790. lastY = y;
  6791. }
  6792. drawX();
  6793. }
  6794. function _getSegmentMethod(line) {
  6795. const opts = line.options;
  6796. const borderDash = opts.borderDash && opts.borderDash.length;
  6797. const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
  6798. return useFastPath ? fastPathSegment : pathSegment;
  6799. }
  6800. function _getInterpolationMethod(options) {
  6801. if (options.stepped) {
  6802. return helpers_segment._steppedInterpolation;
  6803. }
  6804. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  6805. return helpers_segment._bezierInterpolation;
  6806. }
  6807. return helpers_segment._pointInLine;
  6808. }
  6809. function strokePathWithCache(ctx, line, start, count) {
  6810. let path = line._path;
  6811. if (!path) {
  6812. path = line._path = new Path2D();
  6813. if (line.path(path, start, count)) {
  6814. path.closePath();
  6815. }
  6816. }
  6817. setStyle(ctx, line.options);
  6818. ctx.stroke(path);
  6819. }
  6820. function strokePathDirect(ctx, line, start, count) {
  6821. const { segments , options } = line;
  6822. const segmentMethod = _getSegmentMethod(line);
  6823. for (const segment of segments){
  6824. setStyle(ctx, options, segment.style);
  6825. ctx.beginPath();
  6826. if (segmentMethod(ctx, line, segment, {
  6827. start,
  6828. end: start + count - 1
  6829. })) {
  6830. ctx.closePath();
  6831. }
  6832. ctx.stroke();
  6833. }
  6834. }
  6835. const usePath2D = typeof Path2D === 'function';
  6836. function draw(ctx, line, start, count) {
  6837. if (usePath2D && !line.options.segment) {
  6838. strokePathWithCache(ctx, line, start, count);
  6839. } else {
  6840. strokePathDirect(ctx, line, start, count);
  6841. }
  6842. }
  6843. class LineElement extends Element {
  6844. static id = 'line';
  6845. static defaults = {
  6846. borderCapStyle: 'butt',
  6847. borderDash: [],
  6848. borderDashOffset: 0,
  6849. borderJoinStyle: 'miter',
  6850. borderWidth: 3,
  6851. capBezierPoints: true,
  6852. cubicInterpolationMode: 'default',
  6853. fill: false,
  6854. spanGaps: false,
  6855. stepped: false,
  6856. tension: 0
  6857. };
  6858. static defaultRoutes = {
  6859. backgroundColor: 'backgroundColor',
  6860. borderColor: 'borderColor'
  6861. };
  6862. static descriptors = {
  6863. _scriptable: true,
  6864. _indexable: (name)=>name !== 'borderDash' && name !== 'fill'
  6865. };
  6866. constructor(cfg){
  6867. super();
  6868. this.animated = true;
  6869. this.options = undefined;
  6870. this._chart = undefined;
  6871. this._loop = undefined;
  6872. this._fullLoop = undefined;
  6873. this._path = undefined;
  6874. this._points = undefined;
  6875. this._segments = undefined;
  6876. this._decimated = false;
  6877. this._pointsUpdated = false;
  6878. this._datasetIndex = undefined;
  6879. if (cfg) {
  6880. Object.assign(this, cfg);
  6881. }
  6882. }
  6883. updateControlPoints(chartArea, indexAxis) {
  6884. const options = this.options;
  6885. if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
  6886. const loop = options.spanGaps ? this._loop : this._fullLoop;
  6887. helpers_segment._updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
  6888. this._pointsUpdated = true;
  6889. }
  6890. }
  6891. set points(points) {
  6892. this._points = points;
  6893. delete this._segments;
  6894. delete this._path;
  6895. this._pointsUpdated = false;
  6896. }
  6897. get points() {
  6898. return this._points;
  6899. }
  6900. get segments() {
  6901. return this._segments || (this._segments = helpers_segment._computeSegments(this, this.options.segment));
  6902. }
  6903. first() {
  6904. const segments = this.segments;
  6905. const points = this.points;
  6906. return segments.length && points[segments[0].start];
  6907. }
  6908. last() {
  6909. const segments = this.segments;
  6910. const points = this.points;
  6911. const count = segments.length;
  6912. return count && points[segments[count - 1].end];
  6913. }
  6914. interpolate(point, property) {
  6915. const options = this.options;
  6916. const value = point[property];
  6917. const points = this.points;
  6918. const segments = helpers_segment._boundSegments(this, {
  6919. property,
  6920. start: value,
  6921. end: value
  6922. });
  6923. if (!segments.length) {
  6924. return;
  6925. }
  6926. const result = [];
  6927. const _interpolate = _getInterpolationMethod(options);
  6928. let i, ilen;
  6929. for(i = 0, ilen = segments.length; i < ilen; ++i){
  6930. const { start , end } = segments[i];
  6931. const p1 = points[start];
  6932. const p2 = points[end];
  6933. if (p1 === p2) {
  6934. result.push(p1);
  6935. continue;
  6936. }
  6937. const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
  6938. const interpolated = _interpolate(p1, p2, t, options.stepped);
  6939. interpolated[property] = point[property];
  6940. result.push(interpolated);
  6941. }
  6942. return result.length === 1 ? result[0] : result;
  6943. }
  6944. pathSegment(ctx, segment, params) {
  6945. const segmentMethod = _getSegmentMethod(this);
  6946. return segmentMethod(ctx, this, segment, params);
  6947. }
  6948. path(ctx, start, count) {
  6949. const segments = this.segments;
  6950. const segmentMethod = _getSegmentMethod(this);
  6951. let loop = this._loop;
  6952. start = start || 0;
  6953. count = count || this.points.length - start;
  6954. for (const segment of segments){
  6955. loop &= segmentMethod(ctx, this, segment, {
  6956. start,
  6957. end: start + count - 1
  6958. });
  6959. }
  6960. return !!loop;
  6961. }
  6962. draw(ctx, chartArea, start, count) {
  6963. const options = this.options || {};
  6964. const points = this.points || [];
  6965. if (points.length && options.borderWidth) {
  6966. ctx.save();
  6967. draw(ctx, this, start, count);
  6968. ctx.restore();
  6969. }
  6970. if (this.animated) {
  6971. this._pointsUpdated = false;
  6972. this._path = undefined;
  6973. }
  6974. }
  6975. }
  6976. function inRange$1(el, pos, axis, useFinalPosition) {
  6977. const options = el.options;
  6978. const { [axis]: value } = el.getProps([
  6979. axis
  6980. ], useFinalPosition);
  6981. return Math.abs(pos - value) < options.radius + options.hitRadius;
  6982. }
  6983. class PointElement extends Element {
  6984. static id = 'point';
  6985. parsed;
  6986. skip;
  6987. stop;
  6988. /**
  6989. * @type {any}
  6990. */ static defaults = {
  6991. borderWidth: 1,
  6992. hitRadius: 1,
  6993. hoverBorderWidth: 1,
  6994. hoverRadius: 4,
  6995. pointStyle: 'circle',
  6996. radius: 3,
  6997. rotation: 0
  6998. };
  6999. /**
  7000. * @type {any}
  7001. */ static defaultRoutes = {
  7002. backgroundColor: 'backgroundColor',
  7003. borderColor: 'borderColor'
  7004. };
  7005. constructor(cfg){
  7006. super();
  7007. this.options = undefined;
  7008. this.parsed = undefined;
  7009. this.skip = undefined;
  7010. this.stop = undefined;
  7011. if (cfg) {
  7012. Object.assign(this, cfg);
  7013. }
  7014. }
  7015. inRange(mouseX, mouseY, useFinalPosition) {
  7016. const options = this.options;
  7017. const { x , y } = this.getProps([
  7018. 'x',
  7019. 'y'
  7020. ], useFinalPosition);
  7021. return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
  7022. }
  7023. inXRange(mouseX, useFinalPosition) {
  7024. return inRange$1(this, mouseX, 'x', useFinalPosition);
  7025. }
  7026. inYRange(mouseY, useFinalPosition) {
  7027. return inRange$1(this, mouseY, 'y', useFinalPosition);
  7028. }
  7029. getCenterPoint(useFinalPosition) {
  7030. const { x , y } = this.getProps([
  7031. 'x',
  7032. 'y'
  7033. ], useFinalPosition);
  7034. return {
  7035. x,
  7036. y
  7037. };
  7038. }
  7039. size(options) {
  7040. options = options || this.options || {};
  7041. let radius = options.radius || 0;
  7042. radius = Math.max(radius, radius && options.hoverRadius || 0);
  7043. const borderWidth = radius && options.borderWidth || 0;
  7044. return (radius + borderWidth) * 2;
  7045. }
  7046. draw(ctx, area) {
  7047. const options = this.options;
  7048. if (this.skip || options.radius < 0.1 || !helpers_segment._isPointInArea(this, area, this.size(options) / 2)) {
  7049. return;
  7050. }
  7051. ctx.strokeStyle = options.borderColor;
  7052. ctx.lineWidth = options.borderWidth;
  7053. ctx.fillStyle = options.backgroundColor;
  7054. helpers_segment.drawPoint(ctx, options, this.x, this.y);
  7055. }
  7056. getRange() {
  7057. const options = this.options || {};
  7058. // @ts-expect-error Fallbacks should never be hit in practice
  7059. return options.radius + options.hitRadius;
  7060. }
  7061. }
  7062. function getBarBounds(bar, useFinalPosition) {
  7063. const { x , y , base , width , height } = bar.getProps([
  7064. 'x',
  7065. 'y',
  7066. 'base',
  7067. 'width',
  7068. 'height'
  7069. ], useFinalPosition);
  7070. let left, right, top, bottom, half;
  7071. if (bar.horizontal) {
  7072. half = height / 2;
  7073. left = Math.min(x, base);
  7074. right = Math.max(x, base);
  7075. top = y - half;
  7076. bottom = y + half;
  7077. } else {
  7078. half = width / 2;
  7079. left = x - half;
  7080. right = x + half;
  7081. top = Math.min(y, base);
  7082. bottom = Math.max(y, base);
  7083. }
  7084. return {
  7085. left,
  7086. top,
  7087. right,
  7088. bottom
  7089. };
  7090. }
  7091. function skipOrLimit(skip, value, min, max) {
  7092. return skip ? 0 : helpers_segment._limitValue(value, min, max);
  7093. }
  7094. function parseBorderWidth(bar, maxW, maxH) {
  7095. const value = bar.options.borderWidth;
  7096. const skip = bar.borderSkipped;
  7097. const o = helpers_segment.toTRBL(value);
  7098. return {
  7099. t: skipOrLimit(skip.top, o.top, 0, maxH),
  7100. r: skipOrLimit(skip.right, o.right, 0, maxW),
  7101. b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
  7102. l: skipOrLimit(skip.left, o.left, 0, maxW)
  7103. };
  7104. }
  7105. function parseBorderRadius(bar, maxW, maxH) {
  7106. const { enableBorderRadius } = bar.getProps([
  7107. 'enableBorderRadius'
  7108. ]);
  7109. const value = bar.options.borderRadius;
  7110. const o = helpers_segment.toTRBLCorners(value);
  7111. const maxR = Math.min(maxW, maxH);
  7112. const skip = bar.borderSkipped;
  7113. const enableBorder = enableBorderRadius || helpers_segment.isObject(value);
  7114. return {
  7115. topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
  7116. topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
  7117. bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
  7118. bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
  7119. };
  7120. }
  7121. function boundingRects(bar) {
  7122. const bounds = getBarBounds(bar);
  7123. const width = bounds.right - bounds.left;
  7124. const height = bounds.bottom - bounds.top;
  7125. const border = parseBorderWidth(bar, width / 2, height / 2);
  7126. const radius = parseBorderRadius(bar, width / 2, height / 2);
  7127. return {
  7128. outer: {
  7129. x: bounds.left,
  7130. y: bounds.top,
  7131. w: width,
  7132. h: height,
  7133. radius
  7134. },
  7135. inner: {
  7136. x: bounds.left + border.l,
  7137. y: bounds.top + border.t,
  7138. w: width - border.l - border.r,
  7139. h: height - border.t - border.b,
  7140. radius: {
  7141. topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
  7142. topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
  7143. bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
  7144. bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
  7145. }
  7146. }
  7147. };
  7148. }
  7149. function inRange(bar, x, y, useFinalPosition) {
  7150. const skipX = x === null;
  7151. const skipY = y === null;
  7152. const skipBoth = skipX && skipY;
  7153. const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
  7154. return bounds && (skipX || helpers_segment._isBetween(x, bounds.left, bounds.right)) && (skipY || helpers_segment._isBetween(y, bounds.top, bounds.bottom));
  7155. }
  7156. function hasRadius(radius) {
  7157. return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  7158. }
  7159. function addNormalRectPath(ctx, rect) {
  7160. ctx.rect(rect.x, rect.y, rect.w, rect.h);
  7161. }
  7162. function inflateRect(rect, amount, refRect = {}) {
  7163. const x = rect.x !== refRect.x ? -amount : 0;
  7164. const y = rect.y !== refRect.y ? -amount : 0;
  7165. const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
  7166. const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
  7167. return {
  7168. x: rect.x + x,
  7169. y: rect.y + y,
  7170. w: rect.w + w,
  7171. h: rect.h + h,
  7172. radius: rect.radius
  7173. };
  7174. }
  7175. class BarElement extends Element {
  7176. static id = 'bar';
  7177. static defaults = {
  7178. borderSkipped: 'start',
  7179. borderWidth: 0,
  7180. borderRadius: 0,
  7181. inflateAmount: 'auto',
  7182. pointStyle: undefined
  7183. };
  7184. static defaultRoutes = {
  7185. backgroundColor: 'backgroundColor',
  7186. borderColor: 'borderColor'
  7187. };
  7188. constructor(cfg){
  7189. super();
  7190. this.options = undefined;
  7191. this.horizontal = undefined;
  7192. this.base = undefined;
  7193. this.width = undefined;
  7194. this.height = undefined;
  7195. this.inflateAmount = undefined;
  7196. if (cfg) {
  7197. Object.assign(this, cfg);
  7198. }
  7199. }
  7200. draw(ctx) {
  7201. const { inflateAmount , options: { borderColor , backgroundColor } } = this;
  7202. const { inner , outer } = boundingRects(this);
  7203. const addRectPath = hasRadius(outer.radius) ? helpers_segment.addRoundedRectPath : addNormalRectPath;
  7204. ctx.save();
  7205. if (outer.w !== inner.w || outer.h !== inner.h) {
  7206. ctx.beginPath();
  7207. addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
  7208. ctx.clip();
  7209. addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
  7210. ctx.fillStyle = borderColor;
  7211. ctx.fill('evenodd');
  7212. }
  7213. ctx.beginPath();
  7214. addRectPath(ctx, inflateRect(inner, inflateAmount));
  7215. ctx.fillStyle = backgroundColor;
  7216. ctx.fill();
  7217. ctx.restore();
  7218. }
  7219. inRange(mouseX, mouseY, useFinalPosition) {
  7220. return inRange(this, mouseX, mouseY, useFinalPosition);
  7221. }
  7222. inXRange(mouseX, useFinalPosition) {
  7223. return inRange(this, mouseX, null, useFinalPosition);
  7224. }
  7225. inYRange(mouseY, useFinalPosition) {
  7226. return inRange(this, null, mouseY, useFinalPosition);
  7227. }
  7228. getCenterPoint(useFinalPosition) {
  7229. const { x , y , base , horizontal } = this.getProps([
  7230. 'x',
  7231. 'y',
  7232. 'base',
  7233. 'horizontal'
  7234. ], useFinalPosition);
  7235. return {
  7236. x: horizontal ? (x + base) / 2 : x,
  7237. y: horizontal ? y : (y + base) / 2
  7238. };
  7239. }
  7240. getRange(axis) {
  7241. return axis === 'x' ? this.width / 2 : this.height / 2;
  7242. }
  7243. }
  7244. var elements = /*#__PURE__*/Object.freeze({
  7245. __proto__: null,
  7246. ArcElement: ArcElement,
  7247. BarElement: BarElement,
  7248. LineElement: LineElement,
  7249. PointElement: PointElement
  7250. });
  7251. const BORDER_COLORS = [
  7252. 'rgb(54, 162, 235)',
  7253. 'rgb(255, 99, 132)',
  7254. 'rgb(255, 159, 64)',
  7255. 'rgb(255, 205, 86)',
  7256. 'rgb(75, 192, 192)',
  7257. 'rgb(153, 102, 255)',
  7258. 'rgb(201, 203, 207)' // grey
  7259. ];
  7260. // Border colors with 50% transparency
  7261. const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
  7262. function getBorderColor(i) {
  7263. return BORDER_COLORS[i % BORDER_COLORS.length];
  7264. }
  7265. function getBackgroundColor(i) {
  7266. return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
  7267. }
  7268. function colorizeDefaultDataset(dataset, i) {
  7269. dataset.borderColor = getBorderColor(i);
  7270. dataset.backgroundColor = getBackgroundColor(i);
  7271. return ++i;
  7272. }
  7273. function colorizeDoughnutDataset(dataset, i) {
  7274. dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
  7275. return i;
  7276. }
  7277. function colorizePolarAreaDataset(dataset, i) {
  7278. dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
  7279. return i;
  7280. }
  7281. function getColorizer(chart) {
  7282. let i = 0;
  7283. return (dataset, datasetIndex)=>{
  7284. const controller = chart.getDatasetMeta(datasetIndex).controller;
  7285. if (controller instanceof DoughnutController) {
  7286. i = colorizeDoughnutDataset(dataset, i);
  7287. } else if (controller instanceof PolarAreaController) {
  7288. i = colorizePolarAreaDataset(dataset, i);
  7289. } else if (controller) {
  7290. i = colorizeDefaultDataset(dataset, i);
  7291. }
  7292. };
  7293. }
  7294. function containsColorsDefinitions(descriptors) {
  7295. let k;
  7296. for(k in descriptors){
  7297. if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
  7298. return true;
  7299. }
  7300. }
  7301. return false;
  7302. }
  7303. function containsColorsDefinition(descriptor) {
  7304. return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
  7305. }
  7306. var plugin_colors = {
  7307. id: 'colors',
  7308. defaults: {
  7309. enabled: true,
  7310. forceOverride: false
  7311. },
  7312. beforeLayout (chart, _args, options) {
  7313. if (!options.enabled) {
  7314. return;
  7315. }
  7316. const { data: { datasets } , options: chartOptions } = chart.config;
  7317. const { elements } = chartOptions;
  7318. if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) {
  7319. return;
  7320. }
  7321. const colorizer = getColorizer(chart);
  7322. datasets.forEach(colorizer);
  7323. }
  7324. };
  7325. function lttbDecimation(data, start, count, availableWidth, options) {
  7326. const samples = options.samples || availableWidth;
  7327. if (samples >= count) {
  7328. return data.slice(start, start + count);
  7329. }
  7330. const decimated = [];
  7331. const bucketWidth = (count - 2) / (samples - 2);
  7332. let sampledIndex = 0;
  7333. const endIndex = start + count - 1;
  7334. let a = start;
  7335. let i, maxAreaPoint, maxArea, area, nextA;
  7336. decimated[sampledIndex++] = data[a];
  7337. for(i = 0; i < samples - 2; i++){
  7338. let avgX = 0;
  7339. let avgY = 0;
  7340. let j;
  7341. const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
  7342. const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
  7343. const avgRangeLength = avgRangeEnd - avgRangeStart;
  7344. for(j = avgRangeStart; j < avgRangeEnd; j++){
  7345. avgX += data[j].x;
  7346. avgY += data[j].y;
  7347. }
  7348. avgX /= avgRangeLength;
  7349. avgY /= avgRangeLength;
  7350. const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
  7351. const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
  7352. const { x: pointAx , y: pointAy } = data[a];
  7353. maxArea = area = -1;
  7354. for(j = rangeOffs; j < rangeTo; j++){
  7355. area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
  7356. if (area > maxArea) {
  7357. maxArea = area;
  7358. maxAreaPoint = data[j];
  7359. nextA = j;
  7360. }
  7361. }
  7362. decimated[sampledIndex++] = maxAreaPoint;
  7363. a = nextA;
  7364. }
  7365. decimated[sampledIndex++] = data[endIndex];
  7366. return decimated;
  7367. }
  7368. function minMaxDecimation(data, start, count, availableWidth) {
  7369. let avgX = 0;
  7370. let countX = 0;
  7371. let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
  7372. const decimated = [];
  7373. const endIndex = start + count - 1;
  7374. const xMin = data[start].x;
  7375. const xMax = data[endIndex].x;
  7376. const dx = xMax - xMin;
  7377. for(i = start; i < start + count; ++i){
  7378. point = data[i];
  7379. x = (point.x - xMin) / dx * availableWidth;
  7380. y = point.y;
  7381. const truncX = x | 0;
  7382. if (truncX === prevX) {
  7383. if (y < minY) {
  7384. minY = y;
  7385. minIndex = i;
  7386. } else if (y > maxY) {
  7387. maxY = y;
  7388. maxIndex = i;
  7389. }
  7390. avgX = (countX * avgX + point.x) / ++countX;
  7391. } else {
  7392. const lastIndex = i - 1;
  7393. if (!helpers_segment.isNullOrUndef(minIndex) && !helpers_segment.isNullOrUndef(maxIndex)) {
  7394. const intermediateIndex1 = Math.min(minIndex, maxIndex);
  7395. const intermediateIndex2 = Math.max(minIndex, maxIndex);
  7396. if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
  7397. decimated.push({
  7398. ...data[intermediateIndex1],
  7399. x: avgX
  7400. });
  7401. }
  7402. if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
  7403. decimated.push({
  7404. ...data[intermediateIndex2],
  7405. x: avgX
  7406. });
  7407. }
  7408. }
  7409. if (i > 0 && lastIndex !== startIndex) {
  7410. decimated.push(data[lastIndex]);
  7411. }
  7412. decimated.push(point);
  7413. prevX = truncX;
  7414. countX = 0;
  7415. minY = maxY = y;
  7416. minIndex = maxIndex = startIndex = i;
  7417. }
  7418. }
  7419. return decimated;
  7420. }
  7421. function cleanDecimatedDataset(dataset) {
  7422. if (dataset._decimated) {
  7423. const data = dataset._data;
  7424. delete dataset._decimated;
  7425. delete dataset._data;
  7426. Object.defineProperty(dataset, 'data', {
  7427. configurable: true,
  7428. enumerable: true,
  7429. writable: true,
  7430. value: data
  7431. });
  7432. }
  7433. }
  7434. function cleanDecimatedData(chart) {
  7435. chart.data.datasets.forEach((dataset)=>{
  7436. cleanDecimatedDataset(dataset);
  7437. });
  7438. }
  7439. function getStartAndCountOfVisiblePointsSimplified(meta, points) {
  7440. const pointCount = points.length;
  7441. let start = 0;
  7442. let count;
  7443. const { iScale } = meta;
  7444. const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
  7445. if (minDefined) {
  7446. start = helpers_segment._limitValue(helpers_segment._lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
  7447. }
  7448. if (maxDefined) {
  7449. count = helpers_segment._limitValue(helpers_segment._lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
  7450. } else {
  7451. count = pointCount - start;
  7452. }
  7453. return {
  7454. start,
  7455. count
  7456. };
  7457. }
  7458. var plugin_decimation = {
  7459. id: 'decimation',
  7460. defaults: {
  7461. algorithm: 'min-max',
  7462. enabled: false
  7463. },
  7464. beforeElementsUpdate: (chart, args, options)=>{
  7465. if (!options.enabled) {
  7466. cleanDecimatedData(chart);
  7467. return;
  7468. }
  7469. const availableWidth = chart.width;
  7470. chart.data.datasets.forEach((dataset, datasetIndex)=>{
  7471. const { _data , indexAxis } = dataset;
  7472. const meta = chart.getDatasetMeta(datasetIndex);
  7473. const data = _data || dataset.data;
  7474. if (helpers_segment.resolve([
  7475. indexAxis,
  7476. chart.options.indexAxis
  7477. ]) === 'y') {
  7478. return;
  7479. }
  7480. if (!meta.controller.supportsDecimation) {
  7481. return;
  7482. }
  7483. const xAxis = chart.scales[meta.xAxisID];
  7484. if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
  7485. return;
  7486. }
  7487. if (chart.options.parsing) {
  7488. return;
  7489. }
  7490. let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
  7491. const threshold = options.threshold || 4 * availableWidth;
  7492. if (count <= threshold) {
  7493. cleanDecimatedDataset(dataset);
  7494. return;
  7495. }
  7496. if (helpers_segment.isNullOrUndef(_data)) {
  7497. dataset._data = data;
  7498. delete dataset.data;
  7499. Object.defineProperty(dataset, 'data', {
  7500. configurable: true,
  7501. enumerable: true,
  7502. get: function() {
  7503. return this._decimated;
  7504. },
  7505. set: function(d) {
  7506. this._data = d;
  7507. }
  7508. });
  7509. }
  7510. let decimated;
  7511. switch(options.algorithm){
  7512. case 'lttb':
  7513. decimated = lttbDecimation(data, start, count, availableWidth, options);
  7514. break;
  7515. case 'min-max':
  7516. decimated = minMaxDecimation(data, start, count, availableWidth);
  7517. break;
  7518. default:
  7519. throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
  7520. }
  7521. dataset._decimated = decimated;
  7522. });
  7523. },
  7524. destroy (chart) {
  7525. cleanDecimatedData(chart);
  7526. }
  7527. };
  7528. function _segments(line, target, property) {
  7529. const segments = line.segments;
  7530. const points = line.points;
  7531. const tpoints = target.points;
  7532. const parts = [];
  7533. for (const segment of segments){
  7534. let { start , end } = segment;
  7535. end = _findSegmentEnd(start, end, points);
  7536. const bounds = _getBounds(property, points[start], points[end], segment.loop);
  7537. if (!target.segments) {
  7538. parts.push({
  7539. source: segment,
  7540. target: bounds,
  7541. start: points[start],
  7542. end: points[end]
  7543. });
  7544. continue;
  7545. }
  7546. const targetSegments = helpers_segment._boundSegments(target, bounds);
  7547. for (const tgt of targetSegments){
  7548. const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
  7549. const fillSources = helpers_segment._boundSegment(segment, points, subBounds);
  7550. for (const fillSource of fillSources){
  7551. parts.push({
  7552. source: fillSource,
  7553. target: tgt,
  7554. start: {
  7555. [property]: _getEdge(bounds, subBounds, 'start', Math.max)
  7556. },
  7557. end: {
  7558. [property]: _getEdge(bounds, subBounds, 'end', Math.min)
  7559. }
  7560. });
  7561. }
  7562. }
  7563. }
  7564. return parts;
  7565. }
  7566. function _getBounds(property, first, last, loop) {
  7567. if (loop) {
  7568. return;
  7569. }
  7570. let start = first[property];
  7571. let end = last[property];
  7572. if (property === 'angle') {
  7573. start = helpers_segment._normalizeAngle(start);
  7574. end = helpers_segment._normalizeAngle(end);
  7575. }
  7576. return {
  7577. property,
  7578. start,
  7579. end
  7580. };
  7581. }
  7582. function _pointsFromSegments(boundary, line) {
  7583. const { x =null , y =null } = boundary || {};
  7584. const linePoints = line.points;
  7585. const points = [];
  7586. line.segments.forEach(({ start , end })=>{
  7587. end = _findSegmentEnd(start, end, linePoints);
  7588. const first = linePoints[start];
  7589. const last = linePoints[end];
  7590. if (y !== null) {
  7591. points.push({
  7592. x: first.x,
  7593. y
  7594. });
  7595. points.push({
  7596. x: last.x,
  7597. y
  7598. });
  7599. } else if (x !== null) {
  7600. points.push({
  7601. x,
  7602. y: first.y
  7603. });
  7604. points.push({
  7605. x,
  7606. y: last.y
  7607. });
  7608. }
  7609. });
  7610. return points;
  7611. }
  7612. function _findSegmentEnd(start, end, points) {
  7613. for(; end > start; end--){
  7614. const point = points[end];
  7615. if (!isNaN(point.x) && !isNaN(point.y)) {
  7616. break;
  7617. }
  7618. }
  7619. return end;
  7620. }
  7621. function _getEdge(a, b, prop, fn) {
  7622. if (a && b) {
  7623. return fn(a[prop], b[prop]);
  7624. }
  7625. return a ? a[prop] : b ? b[prop] : 0;
  7626. }
  7627. function _createBoundaryLine(boundary, line) {
  7628. let points = [];
  7629. let _loop = false;
  7630. if (helpers_segment.isArray(boundary)) {
  7631. _loop = true;
  7632. points = boundary;
  7633. } else {
  7634. points = _pointsFromSegments(boundary, line);
  7635. }
  7636. return points.length ? new LineElement({
  7637. points,
  7638. options: {
  7639. tension: 0
  7640. },
  7641. _loop,
  7642. _fullLoop: _loop
  7643. }) : null;
  7644. }
  7645. function _shouldApplyFill(source) {
  7646. return source && source.fill !== false;
  7647. }
  7648. function _resolveTarget(sources, index, propagate) {
  7649. const source = sources[index];
  7650. let fill = source.fill;
  7651. const visited = [
  7652. index
  7653. ];
  7654. let target;
  7655. if (!propagate) {
  7656. return fill;
  7657. }
  7658. while(fill !== false && visited.indexOf(fill) === -1){
  7659. if (!helpers_segment.isNumberFinite(fill)) {
  7660. return fill;
  7661. }
  7662. target = sources[fill];
  7663. if (!target) {
  7664. return false;
  7665. }
  7666. if (target.visible) {
  7667. return fill;
  7668. }
  7669. visited.push(fill);
  7670. fill = target.fill;
  7671. }
  7672. return false;
  7673. }
  7674. function _decodeFill(line, index, count) {
  7675. const fill = parseFillOption(line);
  7676. if (helpers_segment.isObject(fill)) {
  7677. return isNaN(fill.value) ? false : fill;
  7678. }
  7679. let target = parseFloat(fill);
  7680. if (helpers_segment.isNumberFinite(target) && Math.floor(target) === target) {
  7681. return decodeTargetIndex(fill[0], index, target, count);
  7682. }
  7683. return [
  7684. 'origin',
  7685. 'start',
  7686. 'end',
  7687. 'stack',
  7688. 'shape'
  7689. ].indexOf(fill) >= 0 && fill;
  7690. }
  7691. function decodeTargetIndex(firstCh, index, target, count) {
  7692. if (firstCh === '-' || firstCh === '+') {
  7693. target = index + target;
  7694. }
  7695. if (target === index || target < 0 || target >= count) {
  7696. return false;
  7697. }
  7698. return target;
  7699. }
  7700. function _getTargetPixel(fill, scale) {
  7701. let pixel = null;
  7702. if (fill === 'start') {
  7703. pixel = scale.bottom;
  7704. } else if (fill === 'end') {
  7705. pixel = scale.top;
  7706. } else if (helpers_segment.isObject(fill)) {
  7707. pixel = scale.getPixelForValue(fill.value);
  7708. } else if (scale.getBasePixel) {
  7709. pixel = scale.getBasePixel();
  7710. }
  7711. return pixel;
  7712. }
  7713. function _getTargetValue(fill, scale, startValue) {
  7714. let value;
  7715. if (fill === 'start') {
  7716. value = startValue;
  7717. } else if (fill === 'end') {
  7718. value = scale.options.reverse ? scale.min : scale.max;
  7719. } else if (helpers_segment.isObject(fill)) {
  7720. value = fill.value;
  7721. } else {
  7722. value = scale.getBaseValue();
  7723. }
  7724. return value;
  7725. }
  7726. function parseFillOption(line) {
  7727. const options = line.options;
  7728. const fillOption = options.fill;
  7729. let fill = helpers_segment.valueOrDefault(fillOption && fillOption.target, fillOption);
  7730. if (fill === undefined) {
  7731. fill = !!options.backgroundColor;
  7732. }
  7733. if (fill === false || fill === null) {
  7734. return false;
  7735. }
  7736. if (fill === true) {
  7737. return 'origin';
  7738. }
  7739. return fill;
  7740. }
  7741. function _buildStackLine(source) {
  7742. const { scale , index , line } = source;
  7743. const points = [];
  7744. const segments = line.segments;
  7745. const sourcePoints = line.points;
  7746. const linesBelow = getLinesBelow(scale, index);
  7747. linesBelow.push(_createBoundaryLine({
  7748. x: null,
  7749. y: scale.bottom
  7750. }, line));
  7751. for(let i = 0; i < segments.length; i++){
  7752. const segment = segments[i];
  7753. for(let j = segment.start; j <= segment.end; j++){
  7754. addPointsBelow(points, sourcePoints[j], linesBelow);
  7755. }
  7756. }
  7757. return new LineElement({
  7758. points,
  7759. options: {}
  7760. });
  7761. }
  7762. function getLinesBelow(scale, index) {
  7763. const below = [];
  7764. const metas = scale.getMatchingVisibleMetas('line');
  7765. for(let i = 0; i < metas.length; i++){
  7766. const meta = metas[i];
  7767. if (meta.index === index) {
  7768. break;
  7769. }
  7770. if (!meta.hidden) {
  7771. below.unshift(meta.dataset);
  7772. }
  7773. }
  7774. return below;
  7775. }
  7776. function addPointsBelow(points, sourcePoint, linesBelow) {
  7777. const postponed = [];
  7778. for(let j = 0; j < linesBelow.length; j++){
  7779. const line = linesBelow[j];
  7780. const { first , last , point } = findPoint(line, sourcePoint, 'x');
  7781. if (!point || first && last) {
  7782. continue;
  7783. }
  7784. if (first) {
  7785. postponed.unshift(point);
  7786. } else {
  7787. points.push(point);
  7788. if (!last) {
  7789. break;
  7790. }
  7791. }
  7792. }
  7793. points.push(...postponed);
  7794. }
  7795. function findPoint(line, sourcePoint, property) {
  7796. const point = line.interpolate(sourcePoint, property);
  7797. if (!point) {
  7798. return {};
  7799. }
  7800. const pointValue = point[property];
  7801. const segments = line.segments;
  7802. const linePoints = line.points;
  7803. let first = false;
  7804. let last = false;
  7805. for(let i = 0; i < segments.length; i++){
  7806. const segment = segments[i];
  7807. const firstValue = linePoints[segment.start][property];
  7808. const lastValue = linePoints[segment.end][property];
  7809. if (helpers_segment._isBetween(pointValue, firstValue, lastValue)) {
  7810. first = pointValue === firstValue;
  7811. last = pointValue === lastValue;
  7812. break;
  7813. }
  7814. }
  7815. return {
  7816. first,
  7817. last,
  7818. point
  7819. };
  7820. }
  7821. class simpleArc {
  7822. constructor(opts){
  7823. this.x = opts.x;
  7824. this.y = opts.y;
  7825. this.radius = opts.radius;
  7826. }
  7827. pathSegment(ctx, bounds, opts) {
  7828. const { x , y , radius } = this;
  7829. bounds = bounds || {
  7830. start: 0,
  7831. end: helpers_segment.TAU
  7832. };
  7833. ctx.arc(x, y, radius, bounds.end, bounds.start, true);
  7834. return !opts.bounds;
  7835. }
  7836. interpolate(point) {
  7837. const { x , y , radius } = this;
  7838. const angle = point.angle;
  7839. return {
  7840. x: x + Math.cos(angle) * radius,
  7841. y: y + Math.sin(angle) * radius,
  7842. angle
  7843. };
  7844. }
  7845. }
  7846. function _getTarget(source) {
  7847. const { chart , fill , line } = source;
  7848. if (helpers_segment.isNumberFinite(fill)) {
  7849. return getLineByIndex(chart, fill);
  7850. }
  7851. if (fill === 'stack') {
  7852. return _buildStackLine(source);
  7853. }
  7854. if (fill === 'shape') {
  7855. return true;
  7856. }
  7857. const boundary = computeBoundary(source);
  7858. if (boundary instanceof simpleArc) {
  7859. return boundary;
  7860. }
  7861. return _createBoundaryLine(boundary, line);
  7862. }
  7863. function getLineByIndex(chart, index) {
  7864. const meta = chart.getDatasetMeta(index);
  7865. const visible = meta && chart.isDatasetVisible(index);
  7866. return visible ? meta.dataset : null;
  7867. }
  7868. function computeBoundary(source) {
  7869. const scale = source.scale || {};
  7870. if (scale.getPointPositionForValue) {
  7871. return computeCircularBoundary(source);
  7872. }
  7873. return computeLinearBoundary(source);
  7874. }
  7875. function computeLinearBoundary(source) {
  7876. const { scale ={} , fill } = source;
  7877. const pixel = _getTargetPixel(fill, scale);
  7878. if (helpers_segment.isNumberFinite(pixel)) {
  7879. const horizontal = scale.isHorizontal();
  7880. return {
  7881. x: horizontal ? pixel : null,
  7882. y: horizontal ? null : pixel
  7883. };
  7884. }
  7885. return null;
  7886. }
  7887. function computeCircularBoundary(source) {
  7888. const { scale , fill } = source;
  7889. const options = scale.options;
  7890. const length = scale.getLabels().length;
  7891. const start = options.reverse ? scale.max : scale.min;
  7892. const value = _getTargetValue(fill, scale, start);
  7893. const target = [];
  7894. if (options.grid.circular) {
  7895. const center = scale.getPointPositionForValue(0, start);
  7896. return new simpleArc({
  7897. x: center.x,
  7898. y: center.y,
  7899. radius: scale.getDistanceFromCenterForValue(value)
  7900. });
  7901. }
  7902. for(let i = 0; i < length; ++i){
  7903. target.push(scale.getPointPositionForValue(i, value));
  7904. }
  7905. return target;
  7906. }
  7907. function _drawfill(ctx, source, area) {
  7908. const target = _getTarget(source);
  7909. const { line , scale , axis } = source;
  7910. const lineOpts = line.options;
  7911. const fillOption = lineOpts.fill;
  7912. const color = lineOpts.backgroundColor;
  7913. const { above =color , below =color } = fillOption || {};
  7914. if (target && line.points.length) {
  7915. helpers_segment.clipArea(ctx, area);
  7916. doFill(ctx, {
  7917. line,
  7918. target,
  7919. above,
  7920. below,
  7921. area,
  7922. scale,
  7923. axis
  7924. });
  7925. helpers_segment.unclipArea(ctx);
  7926. }
  7927. }
  7928. function doFill(ctx, cfg) {
  7929. const { line , target , above , below , area , scale } = cfg;
  7930. const property = line._loop ? 'angle' : cfg.axis;
  7931. ctx.save();
  7932. if (property === 'x' && below !== above) {
  7933. clipVertical(ctx, target, area.top);
  7934. fill(ctx, {
  7935. line,
  7936. target,
  7937. color: above,
  7938. scale,
  7939. property
  7940. });
  7941. ctx.restore();
  7942. ctx.save();
  7943. clipVertical(ctx, target, area.bottom);
  7944. }
  7945. fill(ctx, {
  7946. line,
  7947. target,
  7948. color: below,
  7949. scale,
  7950. property
  7951. });
  7952. ctx.restore();
  7953. }
  7954. function clipVertical(ctx, target, clipY) {
  7955. const { segments , points } = target;
  7956. let first = true;
  7957. let lineLoop = false;
  7958. ctx.beginPath();
  7959. for (const segment of segments){
  7960. const { start , end } = segment;
  7961. const firstPoint = points[start];
  7962. const lastPoint = points[_findSegmentEnd(start, end, points)];
  7963. if (first) {
  7964. ctx.moveTo(firstPoint.x, firstPoint.y);
  7965. first = false;
  7966. } else {
  7967. ctx.lineTo(firstPoint.x, clipY);
  7968. ctx.lineTo(firstPoint.x, firstPoint.y);
  7969. }
  7970. lineLoop = !!target.pathSegment(ctx, segment, {
  7971. move: lineLoop
  7972. });
  7973. if (lineLoop) {
  7974. ctx.closePath();
  7975. } else {
  7976. ctx.lineTo(lastPoint.x, clipY);
  7977. }
  7978. }
  7979. ctx.lineTo(target.first().x, clipY);
  7980. ctx.closePath();
  7981. ctx.clip();
  7982. }
  7983. function fill(ctx, cfg) {
  7984. const { line , target , property , color , scale } = cfg;
  7985. const segments = _segments(line, target, property);
  7986. for (const { source: src , target: tgt , start , end } of segments){
  7987. const { style: { backgroundColor =color } = {} } = src;
  7988. const notShape = target !== true;
  7989. ctx.save();
  7990. ctx.fillStyle = backgroundColor;
  7991. clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
  7992. ctx.beginPath();
  7993. const lineLoop = !!line.pathSegment(ctx, src);
  7994. let loop;
  7995. if (notShape) {
  7996. if (lineLoop) {
  7997. ctx.closePath();
  7998. } else {
  7999. interpolatedLineTo(ctx, target, end, property);
  8000. }
  8001. const targetLoop = !!target.pathSegment(ctx, tgt, {
  8002. move: lineLoop,
  8003. reverse: true
  8004. });
  8005. loop = lineLoop && targetLoop;
  8006. if (!loop) {
  8007. interpolatedLineTo(ctx, target, start, property);
  8008. }
  8009. }
  8010. ctx.closePath();
  8011. ctx.fill(loop ? 'evenodd' : 'nonzero');
  8012. ctx.restore();
  8013. }
  8014. }
  8015. function clipBounds(ctx, scale, bounds) {
  8016. const { top , bottom } = scale.chart.chartArea;
  8017. const { property , start , end } = bounds || {};
  8018. if (property === 'x') {
  8019. ctx.beginPath();
  8020. ctx.rect(start, top, end - start, bottom - top);
  8021. ctx.clip();
  8022. }
  8023. }
  8024. function interpolatedLineTo(ctx, target, point, property) {
  8025. const interpolatedPoint = target.interpolate(point, property);
  8026. if (interpolatedPoint) {
  8027. ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  8028. }
  8029. }
  8030. var index = {
  8031. id: 'filler',
  8032. afterDatasetsUpdate (chart, _args, options) {
  8033. const count = (chart.data.datasets || []).length;
  8034. const sources = [];
  8035. let meta, i, line, source;
  8036. for(i = 0; i < count; ++i){
  8037. meta = chart.getDatasetMeta(i);
  8038. line = meta.dataset;
  8039. source = null;
  8040. if (line && line.options && line instanceof LineElement) {
  8041. source = {
  8042. visible: chart.isDatasetVisible(i),
  8043. index: i,
  8044. fill: _decodeFill(line, i, count),
  8045. chart,
  8046. axis: meta.controller.options.indexAxis,
  8047. scale: meta.vScale,
  8048. line
  8049. };
  8050. }
  8051. meta.$filler = source;
  8052. sources.push(source);
  8053. }
  8054. for(i = 0; i < count; ++i){
  8055. source = sources[i];
  8056. if (!source || source.fill === false) {
  8057. continue;
  8058. }
  8059. source.fill = _resolveTarget(sources, i, options.propagate);
  8060. }
  8061. },
  8062. beforeDraw (chart, _args, options) {
  8063. const draw = options.drawTime === 'beforeDraw';
  8064. const metasets = chart.getSortedVisibleDatasetMetas();
  8065. const area = chart.chartArea;
  8066. for(let i = metasets.length - 1; i >= 0; --i){
  8067. const source = metasets[i].$filler;
  8068. if (!source) {
  8069. continue;
  8070. }
  8071. source.line.updateControlPoints(area, source.axis);
  8072. if (draw && source.fill) {
  8073. _drawfill(chart.ctx, source, area);
  8074. }
  8075. }
  8076. },
  8077. beforeDatasetsDraw (chart, _args, options) {
  8078. if (options.drawTime !== 'beforeDatasetsDraw') {
  8079. return;
  8080. }
  8081. const metasets = chart.getSortedVisibleDatasetMetas();
  8082. for(let i = metasets.length - 1; i >= 0; --i){
  8083. const source = metasets[i].$filler;
  8084. if (_shouldApplyFill(source)) {
  8085. _drawfill(chart.ctx, source, chart.chartArea);
  8086. }
  8087. }
  8088. },
  8089. beforeDatasetDraw (chart, args, options) {
  8090. const source = args.meta.$filler;
  8091. if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
  8092. return;
  8093. }
  8094. _drawfill(chart.ctx, source, chart.chartArea);
  8095. },
  8096. defaults: {
  8097. propagate: true,
  8098. drawTime: 'beforeDatasetDraw'
  8099. }
  8100. };
  8101. const getBoxSize = (labelOpts, fontSize)=>{
  8102. let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
  8103. if (labelOpts.usePointStyle) {
  8104. boxHeight = Math.min(boxHeight, fontSize);
  8105. boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
  8106. }
  8107. return {
  8108. boxWidth,
  8109. boxHeight,
  8110. itemHeight: Math.max(fontSize, boxHeight)
  8111. };
  8112. };
  8113. const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  8114. class Legend extends Element {
  8115. constructor(config){
  8116. super();
  8117. this._added = false;
  8118. this.legendHitBoxes = [];
  8119. this._hoveredItem = null;
  8120. this.doughnutMode = false;
  8121. this.chart = config.chart;
  8122. this.options = config.options;
  8123. this.ctx = config.ctx;
  8124. this.legendItems = undefined;
  8125. this.columnSizes = undefined;
  8126. this.lineWidths = undefined;
  8127. this.maxHeight = undefined;
  8128. this.maxWidth = undefined;
  8129. this.top = undefined;
  8130. this.bottom = undefined;
  8131. this.left = undefined;
  8132. this.right = undefined;
  8133. this.height = undefined;
  8134. this.width = undefined;
  8135. this._margins = undefined;
  8136. this.position = undefined;
  8137. this.weight = undefined;
  8138. this.fullSize = undefined;
  8139. }
  8140. update(maxWidth, maxHeight, margins) {
  8141. this.maxWidth = maxWidth;
  8142. this.maxHeight = maxHeight;
  8143. this._margins = margins;
  8144. this.setDimensions();
  8145. this.buildLabels();
  8146. this.fit();
  8147. }
  8148. setDimensions() {
  8149. if (this.isHorizontal()) {
  8150. this.width = this.maxWidth;
  8151. this.left = this._margins.left;
  8152. this.right = this.width;
  8153. } else {
  8154. this.height = this.maxHeight;
  8155. this.top = this._margins.top;
  8156. this.bottom = this.height;
  8157. }
  8158. }
  8159. buildLabels() {
  8160. const labelOpts = this.options.labels || {};
  8161. let legendItems = helpers_segment.callback(labelOpts.generateLabels, [
  8162. this.chart
  8163. ], this) || [];
  8164. if (labelOpts.filter) {
  8165. legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
  8166. }
  8167. if (labelOpts.sort) {
  8168. legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
  8169. }
  8170. if (this.options.reverse) {
  8171. legendItems.reverse();
  8172. }
  8173. this.legendItems = legendItems;
  8174. }
  8175. fit() {
  8176. const { options , ctx } = this;
  8177. if (!options.display) {
  8178. this.width = this.height = 0;
  8179. return;
  8180. }
  8181. const labelOpts = options.labels;
  8182. const labelFont = helpers_segment.toFont(labelOpts.font);
  8183. const fontSize = labelFont.size;
  8184. const titleHeight = this._computeTitleHeight();
  8185. const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
  8186. let width, height;
  8187. ctx.font = labelFont.string;
  8188. if (this.isHorizontal()) {
  8189. width = this.maxWidth;
  8190. height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  8191. } else {
  8192. height = this.maxHeight;
  8193. width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
  8194. }
  8195. this.width = Math.min(width, options.maxWidth || this.maxWidth);
  8196. this.height = Math.min(height, options.maxHeight || this.maxHeight);
  8197. }
  8198. _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
  8199. const { ctx , maxWidth , options: { labels: { padding } } } = this;
  8200. const hitboxes = this.legendHitBoxes = [];
  8201. const lineWidths = this.lineWidths = [
  8202. 0
  8203. ];
  8204. const lineHeight = itemHeight + padding;
  8205. let totalHeight = titleHeight;
  8206. ctx.textAlign = 'left';
  8207. ctx.textBaseline = 'middle';
  8208. let row = -1;
  8209. let top = -lineHeight;
  8210. this.legendItems.forEach((legendItem, i)=>{
  8211. const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
  8212. if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
  8213. totalHeight += lineHeight;
  8214. lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
  8215. top += lineHeight;
  8216. row++;
  8217. }
  8218. hitboxes[i] = {
  8219. left: 0,
  8220. top,
  8221. row,
  8222. width: itemWidth,
  8223. height: itemHeight
  8224. };
  8225. lineWidths[lineWidths.length - 1] += itemWidth + padding;
  8226. });
  8227. return totalHeight;
  8228. }
  8229. _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
  8230. const { ctx , maxHeight , options: { labels: { padding } } } = this;
  8231. const hitboxes = this.legendHitBoxes = [];
  8232. const columnSizes = this.columnSizes = [];
  8233. const heightLimit = maxHeight - titleHeight;
  8234. let totalWidth = padding;
  8235. let currentColWidth = 0;
  8236. let currentColHeight = 0;
  8237. let left = 0;
  8238. let col = 0;
  8239. this.legendItems.forEach((legendItem, i)=>{
  8240. const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
  8241. if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
  8242. totalWidth += currentColWidth + padding;
  8243. columnSizes.push({
  8244. width: currentColWidth,
  8245. height: currentColHeight
  8246. });
  8247. left += currentColWidth + padding;
  8248. col++;
  8249. currentColWidth = currentColHeight = 0;
  8250. }
  8251. hitboxes[i] = {
  8252. left,
  8253. top: currentColHeight,
  8254. col,
  8255. width: itemWidth,
  8256. height: itemHeight
  8257. };
  8258. currentColWidth = Math.max(currentColWidth, itemWidth);
  8259. currentColHeight += itemHeight + padding;
  8260. });
  8261. totalWidth += currentColWidth;
  8262. columnSizes.push({
  8263. width: currentColWidth,
  8264. height: currentColHeight
  8265. });
  8266. return totalWidth;
  8267. }
  8268. adjustHitBoxes() {
  8269. if (!this.options.display) {
  8270. return;
  8271. }
  8272. const titleHeight = this._computeTitleHeight();
  8273. const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
  8274. const rtlHelper = helpers_segment.getRtlAdapter(rtl, this.left, this.width);
  8275. if (this.isHorizontal()) {
  8276. let row = 0;
  8277. let left = helpers_segment._alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8278. for (const hitbox of hitboxes){
  8279. if (row !== hitbox.row) {
  8280. row = hitbox.row;
  8281. left = helpers_segment._alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  8282. }
  8283. hitbox.top += this.top + titleHeight + padding;
  8284. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
  8285. left += hitbox.width + padding;
  8286. }
  8287. } else {
  8288. let col = 0;
  8289. let top = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8290. for (const hitbox of hitboxes){
  8291. if (hitbox.col !== col) {
  8292. col = hitbox.col;
  8293. top = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  8294. }
  8295. hitbox.top = top;
  8296. hitbox.left += this.left + padding;
  8297. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
  8298. top += hitbox.height + padding;
  8299. }
  8300. }
  8301. }
  8302. isHorizontal() {
  8303. return this.options.position === 'top' || this.options.position === 'bottom';
  8304. }
  8305. draw() {
  8306. if (this.options.display) {
  8307. const ctx = this.ctx;
  8308. helpers_segment.clipArea(ctx, this);
  8309. this._draw();
  8310. helpers_segment.unclipArea(ctx);
  8311. }
  8312. }
  8313. _draw() {
  8314. const { options: opts , columnSizes , lineWidths , ctx } = this;
  8315. const { align , labels: labelOpts } = opts;
  8316. const defaultColor = helpers_segment.defaults.color;
  8317. const rtlHelper = helpers_segment.getRtlAdapter(opts.rtl, this.left, this.width);
  8318. const labelFont = helpers_segment.toFont(labelOpts.font);
  8319. const { padding } = labelOpts;
  8320. const fontSize = labelFont.size;
  8321. const halfFontSize = fontSize / 2;
  8322. let cursor;
  8323. this.drawTitle();
  8324. ctx.textAlign = rtlHelper.textAlign('left');
  8325. ctx.textBaseline = 'middle';
  8326. ctx.lineWidth = 0.5;
  8327. ctx.font = labelFont.string;
  8328. const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
  8329. const drawLegendBox = function(x, y, legendItem) {
  8330. if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
  8331. return;
  8332. }
  8333. ctx.save();
  8334. const lineWidth = helpers_segment.valueOrDefault(legendItem.lineWidth, 1);
  8335. ctx.fillStyle = helpers_segment.valueOrDefault(legendItem.fillStyle, defaultColor);
  8336. ctx.lineCap = helpers_segment.valueOrDefault(legendItem.lineCap, 'butt');
  8337. ctx.lineDashOffset = helpers_segment.valueOrDefault(legendItem.lineDashOffset, 0);
  8338. ctx.lineJoin = helpers_segment.valueOrDefault(legendItem.lineJoin, 'miter');
  8339. ctx.lineWidth = lineWidth;
  8340. ctx.strokeStyle = helpers_segment.valueOrDefault(legendItem.strokeStyle, defaultColor);
  8341. ctx.setLineDash(helpers_segment.valueOrDefault(legendItem.lineDash, []));
  8342. if (labelOpts.usePointStyle) {
  8343. const drawOptions = {
  8344. radius: boxHeight * Math.SQRT2 / 2,
  8345. pointStyle: legendItem.pointStyle,
  8346. rotation: legendItem.rotation,
  8347. borderWidth: lineWidth
  8348. };
  8349. const centerX = rtlHelper.xPlus(x, boxWidth / 2);
  8350. const centerY = y + halfFontSize;
  8351. helpers_segment.drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
  8352. } else {
  8353. const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
  8354. const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
  8355. const borderRadius = helpers_segment.toTRBLCorners(legendItem.borderRadius);
  8356. ctx.beginPath();
  8357. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  8358. helpers_segment.addRoundedRectPath(ctx, {
  8359. x: xBoxLeft,
  8360. y: yBoxTop,
  8361. w: boxWidth,
  8362. h: boxHeight,
  8363. radius: borderRadius
  8364. });
  8365. } else {
  8366. ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
  8367. }
  8368. ctx.fill();
  8369. if (lineWidth !== 0) {
  8370. ctx.stroke();
  8371. }
  8372. }
  8373. ctx.restore();
  8374. };
  8375. const fillText = function(x, y, legendItem) {
  8376. helpers_segment.renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
  8377. strikethrough: legendItem.hidden,
  8378. textAlign: rtlHelper.textAlign(legendItem.textAlign)
  8379. });
  8380. };
  8381. const isHorizontal = this.isHorizontal();
  8382. const titleHeight = this._computeTitleHeight();
  8383. if (isHorizontal) {
  8384. cursor = {
  8385. x: helpers_segment._alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
  8386. y: this.top + padding + titleHeight,
  8387. line: 0
  8388. };
  8389. } else {
  8390. cursor = {
  8391. x: this.left + padding,
  8392. y: helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
  8393. line: 0
  8394. };
  8395. }
  8396. helpers_segment.overrideTextDirection(this.ctx, opts.textDirection);
  8397. const lineHeight = itemHeight + padding;
  8398. this.legendItems.forEach((legendItem, i)=>{
  8399. ctx.strokeStyle = legendItem.fontColor;
  8400. ctx.fillStyle = legendItem.fontColor;
  8401. const textWidth = ctx.measureText(legendItem.text).width;
  8402. const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
  8403. const width = boxWidth + halfFontSize + textWidth;
  8404. let x = cursor.x;
  8405. let y = cursor.y;
  8406. rtlHelper.setWidth(this.width);
  8407. if (isHorizontal) {
  8408. if (i > 0 && x + width + padding > this.right) {
  8409. y = cursor.y += lineHeight;
  8410. cursor.line++;
  8411. x = cursor.x = helpers_segment._alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
  8412. }
  8413. } else if (i > 0 && y + lineHeight > this.bottom) {
  8414. x = cursor.x = x + columnSizes[cursor.line].width + padding;
  8415. cursor.line++;
  8416. y = cursor.y = helpers_segment._alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
  8417. }
  8418. const realX = rtlHelper.x(x);
  8419. drawLegendBox(realX, y, legendItem);
  8420. x = helpers_segment._textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
  8421. fillText(rtlHelper.x(x), y, legendItem);
  8422. if (isHorizontal) {
  8423. cursor.x += width + padding;
  8424. } else if (typeof legendItem.text !== 'string') {
  8425. const fontLineHeight = labelFont.lineHeight;
  8426. cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
  8427. } else {
  8428. cursor.y += lineHeight;
  8429. }
  8430. });
  8431. helpers_segment.restoreTextDirection(this.ctx, opts.textDirection);
  8432. }
  8433. drawTitle() {
  8434. const opts = this.options;
  8435. const titleOpts = opts.title;
  8436. const titleFont = helpers_segment.toFont(titleOpts.font);
  8437. const titlePadding = helpers_segment.toPadding(titleOpts.padding);
  8438. if (!titleOpts.display) {
  8439. return;
  8440. }
  8441. const rtlHelper = helpers_segment.getRtlAdapter(opts.rtl, this.left, this.width);
  8442. const ctx = this.ctx;
  8443. const position = titleOpts.position;
  8444. const halfFontSize = titleFont.size / 2;
  8445. const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
  8446. let y;
  8447. let left = this.left;
  8448. let maxWidth = this.width;
  8449. if (this.isHorizontal()) {
  8450. maxWidth = Math.max(...this.lineWidths);
  8451. y = this.top + topPaddingPlusHalfFontSize;
  8452. left = helpers_segment._alignStartEnd(opts.align, left, this.right - maxWidth);
  8453. } else {
  8454. const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
  8455. y = topPaddingPlusHalfFontSize + helpers_segment._alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
  8456. }
  8457. const x = helpers_segment._alignStartEnd(position, left, left + maxWidth);
  8458. ctx.textAlign = rtlHelper.textAlign(helpers_segment._toLeftRightCenter(position));
  8459. ctx.textBaseline = 'middle';
  8460. ctx.strokeStyle = titleOpts.color;
  8461. ctx.fillStyle = titleOpts.color;
  8462. ctx.font = titleFont.string;
  8463. helpers_segment.renderText(ctx, titleOpts.text, x, y, titleFont);
  8464. }
  8465. _computeTitleHeight() {
  8466. const titleOpts = this.options.title;
  8467. const titleFont = helpers_segment.toFont(titleOpts.font);
  8468. const titlePadding = helpers_segment.toPadding(titleOpts.padding);
  8469. return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
  8470. }
  8471. _getLegendItemAt(x, y) {
  8472. let i, hitBox, lh;
  8473. if (helpers_segment._isBetween(x, this.left, this.right) && helpers_segment._isBetween(y, this.top, this.bottom)) {
  8474. lh = this.legendHitBoxes;
  8475. for(i = 0; i < lh.length; ++i){
  8476. hitBox = lh[i];
  8477. if (helpers_segment._isBetween(x, hitBox.left, hitBox.left + hitBox.width) && helpers_segment._isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
  8478. return this.legendItems[i];
  8479. }
  8480. }
  8481. }
  8482. return null;
  8483. }
  8484. handleEvent(e) {
  8485. const opts = this.options;
  8486. if (!isListened(e.type, opts)) {
  8487. return;
  8488. }
  8489. const hoveredItem = this._getLegendItemAt(e.x, e.y);
  8490. if (e.type === 'mousemove' || e.type === 'mouseout') {
  8491. const previous = this._hoveredItem;
  8492. const sameItem = itemsEqual(previous, hoveredItem);
  8493. if (previous && !sameItem) {
  8494. helpers_segment.callback(opts.onLeave, [
  8495. e,
  8496. previous,
  8497. this
  8498. ], this);
  8499. }
  8500. this._hoveredItem = hoveredItem;
  8501. if (hoveredItem && !sameItem) {
  8502. helpers_segment.callback(opts.onHover, [
  8503. e,
  8504. hoveredItem,
  8505. this
  8506. ], this);
  8507. }
  8508. } else if (hoveredItem) {
  8509. helpers_segment.callback(opts.onClick, [
  8510. e,
  8511. hoveredItem,
  8512. this
  8513. ], this);
  8514. }
  8515. }
  8516. }
  8517. function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
  8518. const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
  8519. const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
  8520. return {
  8521. itemWidth,
  8522. itemHeight
  8523. };
  8524. }
  8525. function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
  8526. let legendItemText = legendItem.text;
  8527. if (legendItemText && typeof legendItemText !== 'string') {
  8528. legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
  8529. }
  8530. return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
  8531. }
  8532. function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
  8533. let itemHeight = _itemHeight;
  8534. if (typeof legendItem.text !== 'string') {
  8535. itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
  8536. }
  8537. return itemHeight;
  8538. }
  8539. function calculateLegendItemHeight(legendItem, fontLineHeight) {
  8540. const labelHeight = legendItem.text ? legendItem.text.length : 0;
  8541. return fontLineHeight * labelHeight;
  8542. }
  8543. function isListened(type, opts) {
  8544. if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
  8545. return true;
  8546. }
  8547. if (opts.onClick && (type === 'click' || type === 'mouseup')) {
  8548. return true;
  8549. }
  8550. return false;
  8551. }
  8552. var plugin_legend = {
  8553. id: 'legend',
  8554. _element: Legend,
  8555. start (chart, _args, options) {
  8556. const legend = chart.legend = new Legend({
  8557. ctx: chart.ctx,
  8558. options,
  8559. chart
  8560. });
  8561. layouts.configure(chart, legend, options);
  8562. layouts.addBox(chart, legend);
  8563. },
  8564. stop (chart) {
  8565. layouts.removeBox(chart, chart.legend);
  8566. delete chart.legend;
  8567. },
  8568. beforeUpdate (chart, _args, options) {
  8569. const legend = chart.legend;
  8570. layouts.configure(chart, legend, options);
  8571. legend.options = options;
  8572. },
  8573. afterUpdate (chart) {
  8574. const legend = chart.legend;
  8575. legend.buildLabels();
  8576. legend.adjustHitBoxes();
  8577. },
  8578. afterEvent (chart, args) {
  8579. if (!args.replay) {
  8580. chart.legend.handleEvent(args.event);
  8581. }
  8582. },
  8583. defaults: {
  8584. display: true,
  8585. position: 'top',
  8586. align: 'center',
  8587. fullSize: true,
  8588. reverse: false,
  8589. weight: 1000,
  8590. onClick (e, legendItem, legend) {
  8591. const index = legendItem.datasetIndex;
  8592. const ci = legend.chart;
  8593. if (ci.isDatasetVisible(index)) {
  8594. ci.hide(index);
  8595. legendItem.hidden = true;
  8596. } else {
  8597. ci.show(index);
  8598. legendItem.hidden = false;
  8599. }
  8600. },
  8601. onHover: null,
  8602. onLeave: null,
  8603. labels: {
  8604. color: (ctx)=>ctx.chart.options.color,
  8605. boxWidth: 40,
  8606. padding: 10,
  8607. generateLabels (chart) {
  8608. const datasets = chart.data.datasets;
  8609. const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
  8610. return chart._getSortedDatasetMetas().map((meta)=>{
  8611. const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
  8612. const borderWidth = helpers_segment.toPadding(style.borderWidth);
  8613. return {
  8614. text: datasets[meta.index].label,
  8615. fillStyle: style.backgroundColor,
  8616. fontColor: color,
  8617. hidden: !meta.visible,
  8618. lineCap: style.borderCapStyle,
  8619. lineDash: style.borderDash,
  8620. lineDashOffset: style.borderDashOffset,
  8621. lineJoin: style.borderJoinStyle,
  8622. lineWidth: (borderWidth.width + borderWidth.height) / 4,
  8623. strokeStyle: style.borderColor,
  8624. pointStyle: pointStyle || style.pointStyle,
  8625. rotation: style.rotation,
  8626. textAlign: textAlign || style.textAlign,
  8627. borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
  8628. datasetIndex: meta.index
  8629. };
  8630. }, this);
  8631. }
  8632. },
  8633. title: {
  8634. color: (ctx)=>ctx.chart.options.color,
  8635. display: false,
  8636. position: 'center',
  8637. text: ''
  8638. }
  8639. },
  8640. descriptors: {
  8641. _scriptable: (name)=>!name.startsWith('on'),
  8642. labels: {
  8643. _scriptable: (name)=>![
  8644. 'generateLabels',
  8645. 'filter',
  8646. 'sort'
  8647. ].includes(name)
  8648. }
  8649. }
  8650. };
  8651. class Title extends Element {
  8652. constructor(config){
  8653. super();
  8654. this.chart = config.chart;
  8655. this.options = config.options;
  8656. this.ctx = config.ctx;
  8657. this._padding = undefined;
  8658. this.top = undefined;
  8659. this.bottom = undefined;
  8660. this.left = undefined;
  8661. this.right = undefined;
  8662. this.width = undefined;
  8663. this.height = undefined;
  8664. this.position = undefined;
  8665. this.weight = undefined;
  8666. this.fullSize = undefined;
  8667. }
  8668. update(maxWidth, maxHeight) {
  8669. const opts = this.options;
  8670. this.left = 0;
  8671. this.top = 0;
  8672. if (!opts.display) {
  8673. this.width = this.height = this.right = this.bottom = 0;
  8674. return;
  8675. }
  8676. this.width = this.right = maxWidth;
  8677. this.height = this.bottom = maxHeight;
  8678. const lineCount = helpers_segment.isArray(opts.text) ? opts.text.length : 1;
  8679. this._padding = helpers_segment.toPadding(opts.padding);
  8680. const textSize = lineCount * helpers_segment.toFont(opts.font).lineHeight + this._padding.height;
  8681. if (this.isHorizontal()) {
  8682. this.height = textSize;
  8683. } else {
  8684. this.width = textSize;
  8685. }
  8686. }
  8687. isHorizontal() {
  8688. const pos = this.options.position;
  8689. return pos === 'top' || pos === 'bottom';
  8690. }
  8691. _drawArgs(offset) {
  8692. const { top , left , bottom , right , options } = this;
  8693. const align = options.align;
  8694. let rotation = 0;
  8695. let maxWidth, titleX, titleY;
  8696. if (this.isHorizontal()) {
  8697. titleX = helpers_segment._alignStartEnd(align, left, right);
  8698. titleY = top + offset;
  8699. maxWidth = right - left;
  8700. } else {
  8701. if (options.position === 'left') {
  8702. titleX = left + offset;
  8703. titleY = helpers_segment._alignStartEnd(align, bottom, top);
  8704. rotation = helpers_segment.PI * -0.5;
  8705. } else {
  8706. titleX = right - offset;
  8707. titleY = helpers_segment._alignStartEnd(align, top, bottom);
  8708. rotation = helpers_segment.PI * 0.5;
  8709. }
  8710. maxWidth = bottom - top;
  8711. }
  8712. return {
  8713. titleX,
  8714. titleY,
  8715. maxWidth,
  8716. rotation
  8717. };
  8718. }
  8719. draw() {
  8720. const ctx = this.ctx;
  8721. const opts = this.options;
  8722. if (!opts.display) {
  8723. return;
  8724. }
  8725. const fontOpts = helpers_segment.toFont(opts.font);
  8726. const lineHeight = fontOpts.lineHeight;
  8727. const offset = lineHeight / 2 + this._padding.top;
  8728. const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
  8729. helpers_segment.renderText(ctx, opts.text, 0, 0, fontOpts, {
  8730. color: opts.color,
  8731. maxWidth,
  8732. rotation,
  8733. textAlign: helpers_segment._toLeftRightCenter(opts.align),
  8734. textBaseline: 'middle',
  8735. translation: [
  8736. titleX,
  8737. titleY
  8738. ]
  8739. });
  8740. }
  8741. }
  8742. function createTitle(chart, titleOpts) {
  8743. const title = new Title({
  8744. ctx: chart.ctx,
  8745. options: titleOpts,
  8746. chart
  8747. });
  8748. layouts.configure(chart, title, titleOpts);
  8749. layouts.addBox(chart, title);
  8750. chart.titleBlock = title;
  8751. }
  8752. var plugin_title = {
  8753. id: 'title',
  8754. _element: Title,
  8755. start (chart, _args, options) {
  8756. createTitle(chart, options);
  8757. },
  8758. stop (chart) {
  8759. const titleBlock = chart.titleBlock;
  8760. layouts.removeBox(chart, titleBlock);
  8761. delete chart.titleBlock;
  8762. },
  8763. beforeUpdate (chart, _args, options) {
  8764. const title = chart.titleBlock;
  8765. layouts.configure(chart, title, options);
  8766. title.options = options;
  8767. },
  8768. defaults: {
  8769. align: 'center',
  8770. display: false,
  8771. font: {
  8772. weight: 'bold'
  8773. },
  8774. fullSize: true,
  8775. padding: 10,
  8776. position: 'top',
  8777. text: '',
  8778. weight: 2000
  8779. },
  8780. defaultRoutes: {
  8781. color: 'color'
  8782. },
  8783. descriptors: {
  8784. _scriptable: true,
  8785. _indexable: false
  8786. }
  8787. };
  8788. const map = new WeakMap();
  8789. var plugin_subtitle = {
  8790. id: 'subtitle',
  8791. start (chart, _args, options) {
  8792. const title = new Title({
  8793. ctx: chart.ctx,
  8794. options,
  8795. chart
  8796. });
  8797. layouts.configure(chart, title, options);
  8798. layouts.addBox(chart, title);
  8799. map.set(chart, title);
  8800. },
  8801. stop (chart) {
  8802. layouts.removeBox(chart, map.get(chart));
  8803. map.delete(chart);
  8804. },
  8805. beforeUpdate (chart, _args, options) {
  8806. const title = map.get(chart);
  8807. layouts.configure(chart, title, options);
  8808. title.options = options;
  8809. },
  8810. defaults: {
  8811. align: 'center',
  8812. display: false,
  8813. font: {
  8814. weight: 'normal'
  8815. },
  8816. fullSize: true,
  8817. padding: 0,
  8818. position: 'top',
  8819. text: '',
  8820. weight: 1500
  8821. },
  8822. defaultRoutes: {
  8823. color: 'color'
  8824. },
  8825. descriptors: {
  8826. _scriptable: true,
  8827. _indexable: false
  8828. }
  8829. };
  8830. const positioners = {
  8831. average (items) {
  8832. if (!items.length) {
  8833. return false;
  8834. }
  8835. let i, len;
  8836. let x = 0;
  8837. let y = 0;
  8838. let count = 0;
  8839. for(i = 0, len = items.length; i < len; ++i){
  8840. const el = items[i].element;
  8841. if (el && el.hasValue()) {
  8842. const pos = el.tooltipPosition();
  8843. x += pos.x;
  8844. y += pos.y;
  8845. ++count;
  8846. }
  8847. }
  8848. return {
  8849. x: x / count,
  8850. y: y / count
  8851. };
  8852. },
  8853. nearest (items, eventPosition) {
  8854. if (!items.length) {
  8855. return false;
  8856. }
  8857. let x = eventPosition.x;
  8858. let y = eventPosition.y;
  8859. let minDistance = Number.POSITIVE_INFINITY;
  8860. let i, len, nearestElement;
  8861. for(i = 0, len = items.length; i < len; ++i){
  8862. const el = items[i].element;
  8863. if (el && el.hasValue()) {
  8864. const center = el.getCenterPoint();
  8865. const d = helpers_segment.distanceBetweenPoints(eventPosition, center);
  8866. if (d < minDistance) {
  8867. minDistance = d;
  8868. nearestElement = el;
  8869. }
  8870. }
  8871. }
  8872. if (nearestElement) {
  8873. const tp = nearestElement.tooltipPosition();
  8874. x = tp.x;
  8875. y = tp.y;
  8876. }
  8877. return {
  8878. x,
  8879. y
  8880. };
  8881. }
  8882. };
  8883. function pushOrConcat(base, toPush) {
  8884. if (toPush) {
  8885. if (helpers_segment.isArray(toPush)) {
  8886. Array.prototype.push.apply(base, toPush);
  8887. } else {
  8888. base.push(toPush);
  8889. }
  8890. }
  8891. return base;
  8892. }
  8893. function splitNewlines(str) {
  8894. if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  8895. return str.split('\n');
  8896. }
  8897. return str;
  8898. }
  8899. function createTooltipItem(chart, item) {
  8900. const { element , datasetIndex , index } = item;
  8901. const controller = chart.getDatasetMeta(datasetIndex).controller;
  8902. const { label , value } = controller.getLabelAndValue(index);
  8903. return {
  8904. chart,
  8905. label,
  8906. parsed: controller.getParsed(index),
  8907. raw: chart.data.datasets[datasetIndex].data[index],
  8908. formattedValue: value,
  8909. dataset: controller.getDataset(),
  8910. dataIndex: index,
  8911. datasetIndex,
  8912. element
  8913. };
  8914. }
  8915. function getTooltipSize(tooltip, options) {
  8916. const ctx = tooltip.chart.ctx;
  8917. const { body , footer , title } = tooltip;
  8918. const { boxWidth , boxHeight } = options;
  8919. const bodyFont = helpers_segment.toFont(options.bodyFont);
  8920. const titleFont = helpers_segment.toFont(options.titleFont);
  8921. const footerFont = helpers_segment.toFont(options.footerFont);
  8922. const titleLineCount = title.length;
  8923. const footerLineCount = footer.length;
  8924. const bodyLineItemCount = body.length;
  8925. const padding = helpers_segment.toPadding(options.padding);
  8926. let height = padding.height;
  8927. let width = 0;
  8928. let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
  8929. combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
  8930. if (titleLineCount) {
  8931. height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
  8932. }
  8933. if (combinedBodyLength) {
  8934. const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
  8935. height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
  8936. }
  8937. if (footerLineCount) {
  8938. height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
  8939. }
  8940. let widthPadding = 0;
  8941. const maxLineWidth = function(line) {
  8942. width = Math.max(width, ctx.measureText(line).width + widthPadding);
  8943. };
  8944. ctx.save();
  8945. ctx.font = titleFont.string;
  8946. helpers_segment.each(tooltip.title, maxLineWidth);
  8947. ctx.font = bodyFont.string;
  8948. helpers_segment.each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
  8949. widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
  8950. helpers_segment.each(body, (bodyItem)=>{
  8951. helpers_segment.each(bodyItem.before, maxLineWidth);
  8952. helpers_segment.each(bodyItem.lines, maxLineWidth);
  8953. helpers_segment.each(bodyItem.after, maxLineWidth);
  8954. });
  8955. widthPadding = 0;
  8956. ctx.font = footerFont.string;
  8957. helpers_segment.each(tooltip.footer, maxLineWidth);
  8958. ctx.restore();
  8959. width += padding.width;
  8960. return {
  8961. width,
  8962. height
  8963. };
  8964. }
  8965. function determineYAlign(chart, size) {
  8966. const { y , height } = size;
  8967. if (y < height / 2) {
  8968. return 'top';
  8969. } else if (y > chart.height - height / 2) {
  8970. return 'bottom';
  8971. }
  8972. return 'center';
  8973. }
  8974. function doesNotFitWithAlign(xAlign, chart, options, size) {
  8975. const { x , width } = size;
  8976. const caret = options.caretSize + options.caretPadding;
  8977. if (xAlign === 'left' && x + width + caret > chart.width) {
  8978. return true;
  8979. }
  8980. if (xAlign === 'right' && x - width - caret < 0) {
  8981. return true;
  8982. }
  8983. }
  8984. function determineXAlign(chart, options, size, yAlign) {
  8985. const { x , width } = size;
  8986. const { width: chartWidth , chartArea: { left , right } } = chart;
  8987. let xAlign = 'center';
  8988. if (yAlign === 'center') {
  8989. xAlign = x <= (left + right) / 2 ? 'left' : 'right';
  8990. } else if (x <= width / 2) {
  8991. xAlign = 'left';
  8992. } else if (x >= chartWidth - width / 2) {
  8993. xAlign = 'right';
  8994. }
  8995. if (doesNotFitWithAlign(xAlign, chart, options, size)) {
  8996. xAlign = 'center';
  8997. }
  8998. return xAlign;
  8999. }
  9000. function determineAlignment(chart, options, size) {
  9001. const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
  9002. return {
  9003. xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
  9004. yAlign
  9005. };
  9006. }
  9007. function alignX(size, xAlign) {
  9008. let { x , width } = size;
  9009. if (xAlign === 'right') {
  9010. x -= width;
  9011. } else if (xAlign === 'center') {
  9012. x -= width / 2;
  9013. }
  9014. return x;
  9015. }
  9016. function alignY(size, yAlign, paddingAndSize) {
  9017. let { y , height } = size;
  9018. if (yAlign === 'top') {
  9019. y += paddingAndSize;
  9020. } else if (yAlign === 'bottom') {
  9021. y -= height + paddingAndSize;
  9022. } else {
  9023. y -= height / 2;
  9024. }
  9025. return y;
  9026. }
  9027. function getBackgroundPoint(options, size, alignment, chart) {
  9028. const { caretSize , caretPadding , cornerRadius } = options;
  9029. const { xAlign , yAlign } = alignment;
  9030. const paddingAndSize = caretSize + caretPadding;
  9031. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(cornerRadius);
  9032. let x = alignX(size, xAlign);
  9033. const y = alignY(size, yAlign, paddingAndSize);
  9034. if (yAlign === 'center') {
  9035. if (xAlign === 'left') {
  9036. x += paddingAndSize;
  9037. } else if (xAlign === 'right') {
  9038. x -= paddingAndSize;
  9039. }
  9040. } else if (xAlign === 'left') {
  9041. x -= Math.max(topLeft, bottomLeft) + caretSize;
  9042. } else if (xAlign === 'right') {
  9043. x += Math.max(topRight, bottomRight) + caretSize;
  9044. }
  9045. return {
  9046. x: helpers_segment._limitValue(x, 0, chart.width - size.width),
  9047. y: helpers_segment._limitValue(y, 0, chart.height - size.height)
  9048. };
  9049. }
  9050. function getAlignedX(tooltip, align, options) {
  9051. const padding = helpers_segment.toPadding(options.padding);
  9052. return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
  9053. }
  9054. function getBeforeAfterBodyLines(callback) {
  9055. return pushOrConcat([], splitNewlines(callback));
  9056. }
  9057. function createTooltipContext(parent, tooltip, tooltipItems) {
  9058. return helpers_segment.createContext(parent, {
  9059. tooltip,
  9060. tooltipItems,
  9061. type: 'tooltip'
  9062. });
  9063. }
  9064. function overrideCallbacks(callbacks, context) {
  9065. const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  9066. return override ? callbacks.override(override) : callbacks;
  9067. }
  9068. const defaultCallbacks = {
  9069. beforeTitle: helpers_segment.noop,
  9070. title (tooltipItems) {
  9071. if (tooltipItems.length > 0) {
  9072. const item = tooltipItems[0];
  9073. const labels = item.chart.data.labels;
  9074. const labelCount = labels ? labels.length : 0;
  9075. if (this && this.options && this.options.mode === 'dataset') {
  9076. return item.dataset.label || '';
  9077. } else if (item.label) {
  9078. return item.label;
  9079. } else if (labelCount > 0 && item.dataIndex < labelCount) {
  9080. return labels[item.dataIndex];
  9081. }
  9082. }
  9083. return '';
  9084. },
  9085. afterTitle: helpers_segment.noop,
  9086. beforeBody: helpers_segment.noop,
  9087. beforeLabel: helpers_segment.noop,
  9088. label (tooltipItem) {
  9089. if (this && this.options && this.options.mode === 'dataset') {
  9090. return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
  9091. }
  9092. let label = tooltipItem.dataset.label || '';
  9093. if (label) {
  9094. label += ': ';
  9095. }
  9096. const value = tooltipItem.formattedValue;
  9097. if (!helpers_segment.isNullOrUndef(value)) {
  9098. label += value;
  9099. }
  9100. return label;
  9101. },
  9102. labelColor (tooltipItem) {
  9103. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9104. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9105. return {
  9106. borderColor: options.borderColor,
  9107. backgroundColor: options.backgroundColor,
  9108. borderWidth: options.borderWidth,
  9109. borderDash: options.borderDash,
  9110. borderDashOffset: options.borderDashOffset,
  9111. borderRadius: 0
  9112. };
  9113. },
  9114. labelTextColor () {
  9115. return this.options.bodyColor;
  9116. },
  9117. labelPointStyle (tooltipItem) {
  9118. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  9119. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  9120. return {
  9121. pointStyle: options.pointStyle,
  9122. rotation: options.rotation
  9123. };
  9124. },
  9125. afterLabel: helpers_segment.noop,
  9126. afterBody: helpers_segment.noop,
  9127. beforeFooter: helpers_segment.noop,
  9128. footer: helpers_segment.noop,
  9129. afterFooter: helpers_segment.noop
  9130. };
  9131. function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
  9132. const result = callbacks[name].call(ctx, arg);
  9133. if (typeof result === 'undefined') {
  9134. return defaultCallbacks[name].call(ctx, arg);
  9135. }
  9136. return result;
  9137. }
  9138. class Tooltip extends Element {
  9139. static positioners = positioners;
  9140. constructor(config){
  9141. super();
  9142. this.opacity = 0;
  9143. this._active = [];
  9144. this._eventPosition = undefined;
  9145. this._size = undefined;
  9146. this._cachedAnimations = undefined;
  9147. this._tooltipItems = [];
  9148. this.$animations = undefined;
  9149. this.$context = undefined;
  9150. this.chart = config.chart;
  9151. this.options = config.options;
  9152. this.dataPoints = undefined;
  9153. this.title = undefined;
  9154. this.beforeBody = undefined;
  9155. this.body = undefined;
  9156. this.afterBody = undefined;
  9157. this.footer = undefined;
  9158. this.xAlign = undefined;
  9159. this.yAlign = undefined;
  9160. this.x = undefined;
  9161. this.y = undefined;
  9162. this.height = undefined;
  9163. this.width = undefined;
  9164. this.caretX = undefined;
  9165. this.caretY = undefined;
  9166. this.labelColors = undefined;
  9167. this.labelPointStyles = undefined;
  9168. this.labelTextColors = undefined;
  9169. }
  9170. initialize(options) {
  9171. this.options = options;
  9172. this._cachedAnimations = undefined;
  9173. this.$context = undefined;
  9174. }
  9175. _resolveAnimations() {
  9176. const cached = this._cachedAnimations;
  9177. if (cached) {
  9178. return cached;
  9179. }
  9180. const chart = this.chart;
  9181. const options = this.options.setContext(this.getContext());
  9182. const opts = options.enabled && chart.options.animation && options.animations;
  9183. const animations = new Animations(this.chart, opts);
  9184. if (opts._cacheable) {
  9185. this._cachedAnimations = Object.freeze(animations);
  9186. }
  9187. return animations;
  9188. }
  9189. getContext() {
  9190. return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
  9191. }
  9192. getTitle(context, options) {
  9193. const { callbacks } = options;
  9194. const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
  9195. const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
  9196. const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
  9197. let lines = [];
  9198. lines = pushOrConcat(lines, splitNewlines(beforeTitle));
  9199. lines = pushOrConcat(lines, splitNewlines(title));
  9200. lines = pushOrConcat(lines, splitNewlines(afterTitle));
  9201. return lines;
  9202. }
  9203. getBeforeBody(tooltipItems, options) {
  9204. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
  9205. }
  9206. getBody(tooltipItems, options) {
  9207. const { callbacks } = options;
  9208. const bodyItems = [];
  9209. helpers_segment.each(tooltipItems, (context)=>{
  9210. const bodyItem = {
  9211. before: [],
  9212. lines: [],
  9213. after: []
  9214. };
  9215. const scoped = overrideCallbacks(callbacks, context);
  9216. pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
  9217. pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
  9218. pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
  9219. bodyItems.push(bodyItem);
  9220. });
  9221. return bodyItems;
  9222. }
  9223. getAfterBody(tooltipItems, options) {
  9224. return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
  9225. }
  9226. getFooter(tooltipItems, options) {
  9227. const { callbacks } = options;
  9228. const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
  9229. const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
  9230. const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
  9231. let lines = [];
  9232. lines = pushOrConcat(lines, splitNewlines(beforeFooter));
  9233. lines = pushOrConcat(lines, splitNewlines(footer));
  9234. lines = pushOrConcat(lines, splitNewlines(afterFooter));
  9235. return lines;
  9236. }
  9237. _createItems(options) {
  9238. const active = this._active;
  9239. const data = this.chart.data;
  9240. const labelColors = [];
  9241. const labelPointStyles = [];
  9242. const labelTextColors = [];
  9243. let tooltipItems = [];
  9244. let i, len;
  9245. for(i = 0, len = active.length; i < len; ++i){
  9246. tooltipItems.push(createTooltipItem(this.chart, active[i]));
  9247. }
  9248. if (options.filter) {
  9249. tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
  9250. }
  9251. if (options.itemSort) {
  9252. tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
  9253. }
  9254. helpers_segment.each(tooltipItems, (context)=>{
  9255. const scoped = overrideCallbacks(options.callbacks, context);
  9256. labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
  9257. labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
  9258. labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
  9259. });
  9260. this.labelColors = labelColors;
  9261. this.labelPointStyles = labelPointStyles;
  9262. this.labelTextColors = labelTextColors;
  9263. this.dataPoints = tooltipItems;
  9264. return tooltipItems;
  9265. }
  9266. update(changed, replay) {
  9267. const options = this.options.setContext(this.getContext());
  9268. const active = this._active;
  9269. let properties;
  9270. let tooltipItems = [];
  9271. if (!active.length) {
  9272. if (this.opacity !== 0) {
  9273. properties = {
  9274. opacity: 0
  9275. };
  9276. }
  9277. } else {
  9278. const position = positioners[options.position].call(this, active, this._eventPosition);
  9279. tooltipItems = this._createItems(options);
  9280. this.title = this.getTitle(tooltipItems, options);
  9281. this.beforeBody = this.getBeforeBody(tooltipItems, options);
  9282. this.body = this.getBody(tooltipItems, options);
  9283. this.afterBody = this.getAfterBody(tooltipItems, options);
  9284. this.footer = this.getFooter(tooltipItems, options);
  9285. const size = this._size = getTooltipSize(this, options);
  9286. const positionAndSize = Object.assign({}, position, size);
  9287. const alignment = determineAlignment(this.chart, options, positionAndSize);
  9288. const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
  9289. this.xAlign = alignment.xAlign;
  9290. this.yAlign = alignment.yAlign;
  9291. properties = {
  9292. opacity: 1,
  9293. x: backgroundPoint.x,
  9294. y: backgroundPoint.y,
  9295. width: size.width,
  9296. height: size.height,
  9297. caretX: position.x,
  9298. caretY: position.y
  9299. };
  9300. }
  9301. this._tooltipItems = tooltipItems;
  9302. this.$context = undefined;
  9303. if (properties) {
  9304. this._resolveAnimations().update(this, properties);
  9305. }
  9306. if (changed && options.external) {
  9307. options.external.call(this, {
  9308. chart: this.chart,
  9309. tooltip: this,
  9310. replay
  9311. });
  9312. }
  9313. }
  9314. drawCaret(tooltipPoint, ctx, size, options) {
  9315. const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
  9316. ctx.lineTo(caretPosition.x1, caretPosition.y1);
  9317. ctx.lineTo(caretPosition.x2, caretPosition.y2);
  9318. ctx.lineTo(caretPosition.x3, caretPosition.y3);
  9319. }
  9320. getCaretPosition(tooltipPoint, size, options) {
  9321. const { xAlign , yAlign } = this;
  9322. const { caretSize , cornerRadius } = options;
  9323. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(cornerRadius);
  9324. const { x: ptX , y: ptY } = tooltipPoint;
  9325. const { width , height } = size;
  9326. let x1, x2, x3, y1, y2, y3;
  9327. if (yAlign === 'center') {
  9328. y2 = ptY + height / 2;
  9329. if (xAlign === 'left') {
  9330. x1 = ptX;
  9331. x2 = x1 - caretSize;
  9332. y1 = y2 + caretSize;
  9333. y3 = y2 - caretSize;
  9334. } else {
  9335. x1 = ptX + width;
  9336. x2 = x1 + caretSize;
  9337. y1 = y2 - caretSize;
  9338. y3 = y2 + caretSize;
  9339. }
  9340. x3 = x1;
  9341. } else {
  9342. if (xAlign === 'left') {
  9343. x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
  9344. } else if (xAlign === 'right') {
  9345. x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
  9346. } else {
  9347. x2 = this.caretX;
  9348. }
  9349. if (yAlign === 'top') {
  9350. y1 = ptY;
  9351. y2 = y1 - caretSize;
  9352. x1 = x2 - caretSize;
  9353. x3 = x2 + caretSize;
  9354. } else {
  9355. y1 = ptY + height;
  9356. y2 = y1 + caretSize;
  9357. x1 = x2 + caretSize;
  9358. x3 = x2 - caretSize;
  9359. }
  9360. y3 = y1;
  9361. }
  9362. return {
  9363. x1,
  9364. x2,
  9365. x3,
  9366. y1,
  9367. y2,
  9368. y3
  9369. };
  9370. }
  9371. drawTitle(pt, ctx, options) {
  9372. const title = this.title;
  9373. const length = title.length;
  9374. let titleFont, titleSpacing, i;
  9375. if (length) {
  9376. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9377. pt.x = getAlignedX(this, options.titleAlign, options);
  9378. ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
  9379. ctx.textBaseline = 'middle';
  9380. titleFont = helpers_segment.toFont(options.titleFont);
  9381. titleSpacing = options.titleSpacing;
  9382. ctx.fillStyle = options.titleColor;
  9383. ctx.font = titleFont.string;
  9384. for(i = 0; i < length; ++i){
  9385. ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
  9386. pt.y += titleFont.lineHeight + titleSpacing;
  9387. if (i + 1 === length) {
  9388. pt.y += options.titleMarginBottom - titleSpacing;
  9389. }
  9390. }
  9391. }
  9392. }
  9393. _drawColorBox(ctx, pt, i, rtlHelper, options) {
  9394. const labelColor = this.labelColors[i];
  9395. const labelPointStyle = this.labelPointStyles[i];
  9396. const { boxHeight , boxWidth } = options;
  9397. const bodyFont = helpers_segment.toFont(options.bodyFont);
  9398. const colorX = getAlignedX(this, 'left', options);
  9399. const rtlColorX = rtlHelper.x(colorX);
  9400. const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
  9401. const colorY = pt.y + yOffSet;
  9402. if (options.usePointStyle) {
  9403. const drawOptions = {
  9404. radius: Math.min(boxWidth, boxHeight) / 2,
  9405. pointStyle: labelPointStyle.pointStyle,
  9406. rotation: labelPointStyle.rotation,
  9407. borderWidth: 1
  9408. };
  9409. const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
  9410. const centerY = colorY + boxHeight / 2;
  9411. ctx.strokeStyle = options.multiKeyBackground;
  9412. ctx.fillStyle = options.multiKeyBackground;
  9413. helpers_segment.drawPoint(ctx, drawOptions, centerX, centerY);
  9414. ctx.strokeStyle = labelColor.borderColor;
  9415. ctx.fillStyle = labelColor.backgroundColor;
  9416. helpers_segment.drawPoint(ctx, drawOptions, centerX, centerY);
  9417. } else {
  9418. ctx.lineWidth = helpers_segment.isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1;
  9419. ctx.strokeStyle = labelColor.borderColor;
  9420. ctx.setLineDash(labelColor.borderDash || []);
  9421. ctx.lineDashOffset = labelColor.borderDashOffset || 0;
  9422. const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
  9423. const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
  9424. const borderRadius = helpers_segment.toTRBLCorners(labelColor.borderRadius);
  9425. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  9426. ctx.beginPath();
  9427. ctx.fillStyle = options.multiKeyBackground;
  9428. helpers_segment.addRoundedRectPath(ctx, {
  9429. x: outerX,
  9430. y: colorY,
  9431. w: boxWidth,
  9432. h: boxHeight,
  9433. radius: borderRadius
  9434. });
  9435. ctx.fill();
  9436. ctx.stroke();
  9437. ctx.fillStyle = labelColor.backgroundColor;
  9438. ctx.beginPath();
  9439. helpers_segment.addRoundedRectPath(ctx, {
  9440. x: innerX,
  9441. y: colorY + 1,
  9442. w: boxWidth - 2,
  9443. h: boxHeight - 2,
  9444. radius: borderRadius
  9445. });
  9446. ctx.fill();
  9447. } else {
  9448. ctx.fillStyle = options.multiKeyBackground;
  9449. ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
  9450. ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
  9451. ctx.fillStyle = labelColor.backgroundColor;
  9452. ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
  9453. }
  9454. }
  9455. ctx.fillStyle = this.labelTextColors[i];
  9456. }
  9457. drawBody(pt, ctx, options) {
  9458. const { body } = this;
  9459. const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
  9460. const bodyFont = helpers_segment.toFont(options.bodyFont);
  9461. let bodyLineHeight = bodyFont.lineHeight;
  9462. let xLinePadding = 0;
  9463. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9464. const fillLineOfText = function(line) {
  9465. ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
  9466. pt.y += bodyLineHeight + bodySpacing;
  9467. };
  9468. const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
  9469. let bodyItem, textColor, lines, i, j, ilen, jlen;
  9470. ctx.textAlign = bodyAlign;
  9471. ctx.textBaseline = 'middle';
  9472. ctx.font = bodyFont.string;
  9473. pt.x = getAlignedX(this, bodyAlignForCalculation, options);
  9474. ctx.fillStyle = options.bodyColor;
  9475. helpers_segment.each(this.beforeBody, fillLineOfText);
  9476. xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
  9477. for(i = 0, ilen = body.length; i < ilen; ++i){
  9478. bodyItem = body[i];
  9479. textColor = this.labelTextColors[i];
  9480. ctx.fillStyle = textColor;
  9481. helpers_segment.each(bodyItem.before, fillLineOfText);
  9482. lines = bodyItem.lines;
  9483. if (displayColors && lines.length) {
  9484. this._drawColorBox(ctx, pt, i, rtlHelper, options);
  9485. bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
  9486. }
  9487. for(j = 0, jlen = lines.length; j < jlen; ++j){
  9488. fillLineOfText(lines[j]);
  9489. bodyLineHeight = bodyFont.lineHeight;
  9490. }
  9491. helpers_segment.each(bodyItem.after, fillLineOfText);
  9492. }
  9493. xLinePadding = 0;
  9494. bodyLineHeight = bodyFont.lineHeight;
  9495. helpers_segment.each(this.afterBody, fillLineOfText);
  9496. pt.y -= bodySpacing;
  9497. }
  9498. drawFooter(pt, ctx, options) {
  9499. const footer = this.footer;
  9500. const length = footer.length;
  9501. let footerFont, i;
  9502. if (length) {
  9503. const rtlHelper = helpers_segment.getRtlAdapter(options.rtl, this.x, this.width);
  9504. pt.x = getAlignedX(this, options.footerAlign, options);
  9505. pt.y += options.footerMarginTop;
  9506. ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
  9507. ctx.textBaseline = 'middle';
  9508. footerFont = helpers_segment.toFont(options.footerFont);
  9509. ctx.fillStyle = options.footerColor;
  9510. ctx.font = footerFont.string;
  9511. for(i = 0; i < length; ++i){
  9512. ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
  9513. pt.y += footerFont.lineHeight + options.footerSpacing;
  9514. }
  9515. }
  9516. }
  9517. drawBackground(pt, ctx, tooltipSize, options) {
  9518. const { xAlign , yAlign } = this;
  9519. const { x , y } = pt;
  9520. const { width , height } = tooltipSize;
  9521. const { topLeft , topRight , bottomLeft , bottomRight } = helpers_segment.toTRBLCorners(options.cornerRadius);
  9522. ctx.fillStyle = options.backgroundColor;
  9523. ctx.strokeStyle = options.borderColor;
  9524. ctx.lineWidth = options.borderWidth;
  9525. ctx.beginPath();
  9526. ctx.moveTo(x + topLeft, y);
  9527. if (yAlign === 'top') {
  9528. this.drawCaret(pt, ctx, tooltipSize, options);
  9529. }
  9530. ctx.lineTo(x + width - topRight, y);
  9531. ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
  9532. if (yAlign === 'center' && xAlign === 'right') {
  9533. this.drawCaret(pt, ctx, tooltipSize, options);
  9534. }
  9535. ctx.lineTo(x + width, y + height - bottomRight);
  9536. ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
  9537. if (yAlign === 'bottom') {
  9538. this.drawCaret(pt, ctx, tooltipSize, options);
  9539. }
  9540. ctx.lineTo(x + bottomLeft, y + height);
  9541. ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
  9542. if (yAlign === 'center' && xAlign === 'left') {
  9543. this.drawCaret(pt, ctx, tooltipSize, options);
  9544. }
  9545. ctx.lineTo(x, y + topLeft);
  9546. ctx.quadraticCurveTo(x, y, x + topLeft, y);
  9547. ctx.closePath();
  9548. ctx.fill();
  9549. if (options.borderWidth > 0) {
  9550. ctx.stroke();
  9551. }
  9552. }
  9553. _updateAnimationTarget(options) {
  9554. const chart = this.chart;
  9555. const anims = this.$animations;
  9556. const animX = anims && anims.x;
  9557. const animY = anims && anims.y;
  9558. if (animX || animY) {
  9559. const position = positioners[options.position].call(this, this._active, this._eventPosition);
  9560. if (!position) {
  9561. return;
  9562. }
  9563. const size = this._size = getTooltipSize(this, options);
  9564. const positionAndSize = Object.assign({}, position, this._size);
  9565. const alignment = determineAlignment(chart, options, positionAndSize);
  9566. const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
  9567. if (animX._to !== point.x || animY._to !== point.y) {
  9568. this.xAlign = alignment.xAlign;
  9569. this.yAlign = alignment.yAlign;
  9570. this.width = size.width;
  9571. this.height = size.height;
  9572. this.caretX = position.x;
  9573. this.caretY = position.y;
  9574. this._resolveAnimations().update(this, point);
  9575. }
  9576. }
  9577. }
  9578. _willRender() {
  9579. return !!this.opacity;
  9580. }
  9581. draw(ctx) {
  9582. const options = this.options.setContext(this.getContext());
  9583. let opacity = this.opacity;
  9584. if (!opacity) {
  9585. return;
  9586. }
  9587. this._updateAnimationTarget(options);
  9588. const tooltipSize = {
  9589. width: this.width,
  9590. height: this.height
  9591. };
  9592. const pt = {
  9593. x: this.x,
  9594. y: this.y
  9595. };
  9596. opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
  9597. const padding = helpers_segment.toPadding(options.padding);
  9598. const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
  9599. if (options.enabled && hasTooltipContent) {
  9600. ctx.save();
  9601. ctx.globalAlpha = opacity;
  9602. this.drawBackground(pt, ctx, tooltipSize, options);
  9603. helpers_segment.overrideTextDirection(ctx, options.textDirection);
  9604. pt.y += padding.top;
  9605. this.drawTitle(pt, ctx, options);
  9606. this.drawBody(pt, ctx, options);
  9607. this.drawFooter(pt, ctx, options);
  9608. helpers_segment.restoreTextDirection(ctx, options.textDirection);
  9609. ctx.restore();
  9610. }
  9611. }
  9612. getActiveElements() {
  9613. return this._active || [];
  9614. }
  9615. setActiveElements(activeElements, eventPosition) {
  9616. const lastActive = this._active;
  9617. const active = activeElements.map(({ datasetIndex , index })=>{
  9618. const meta = this.chart.getDatasetMeta(datasetIndex);
  9619. if (!meta) {
  9620. throw new Error('Cannot find a dataset at index ' + datasetIndex);
  9621. }
  9622. return {
  9623. datasetIndex,
  9624. element: meta.data[index],
  9625. index
  9626. };
  9627. });
  9628. const changed = !helpers_segment._elementsEqual(lastActive, active);
  9629. const positionChanged = this._positionChanged(active, eventPosition);
  9630. if (changed || positionChanged) {
  9631. this._active = active;
  9632. this._eventPosition = eventPosition;
  9633. this._ignoreReplayEvents = true;
  9634. this.update(true);
  9635. }
  9636. }
  9637. handleEvent(e, replay, inChartArea = true) {
  9638. if (replay && this._ignoreReplayEvents) {
  9639. return false;
  9640. }
  9641. this._ignoreReplayEvents = false;
  9642. const options = this.options;
  9643. const lastActive = this._active || [];
  9644. const active = this._getActiveElements(e, lastActive, replay, inChartArea);
  9645. const positionChanged = this._positionChanged(active, e);
  9646. const changed = replay || !helpers_segment._elementsEqual(active, lastActive) || positionChanged;
  9647. if (changed) {
  9648. this._active = active;
  9649. if (options.enabled || options.external) {
  9650. this._eventPosition = {
  9651. x: e.x,
  9652. y: e.y
  9653. };
  9654. this.update(true, replay);
  9655. }
  9656. }
  9657. return changed;
  9658. }
  9659. _getActiveElements(e, lastActive, replay, inChartArea) {
  9660. const options = this.options;
  9661. if (e.type === 'mouseout') {
  9662. return [];
  9663. }
  9664. if (!inChartArea) {
  9665. return lastActive;
  9666. }
  9667. const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
  9668. if (options.reverse) {
  9669. active.reverse();
  9670. }
  9671. return active;
  9672. }
  9673. _positionChanged(active, e) {
  9674. const { caretX , caretY , options } = this;
  9675. const position = positioners[options.position].call(this, active, e);
  9676. return position !== false && (caretX !== position.x || caretY !== position.y);
  9677. }
  9678. }
  9679. var plugin_tooltip = {
  9680. id: 'tooltip',
  9681. _element: Tooltip,
  9682. positioners,
  9683. afterInit (chart, _args, options) {
  9684. if (options) {
  9685. chart.tooltip = new Tooltip({
  9686. chart,
  9687. options
  9688. });
  9689. }
  9690. },
  9691. beforeUpdate (chart, _args, options) {
  9692. if (chart.tooltip) {
  9693. chart.tooltip.initialize(options);
  9694. }
  9695. },
  9696. reset (chart, _args, options) {
  9697. if (chart.tooltip) {
  9698. chart.tooltip.initialize(options);
  9699. }
  9700. },
  9701. afterDraw (chart) {
  9702. const tooltip = chart.tooltip;
  9703. if (tooltip && tooltip._willRender()) {
  9704. const args = {
  9705. tooltip
  9706. };
  9707. if (chart.notifyPlugins('beforeTooltipDraw', {
  9708. ...args,
  9709. cancelable: true
  9710. }) === false) {
  9711. return;
  9712. }
  9713. tooltip.draw(chart.ctx);
  9714. chart.notifyPlugins('afterTooltipDraw', args);
  9715. }
  9716. },
  9717. afterEvent (chart, args) {
  9718. if (chart.tooltip) {
  9719. const useFinalPosition = args.replay;
  9720. if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
  9721. args.changed = true;
  9722. }
  9723. }
  9724. },
  9725. defaults: {
  9726. enabled: true,
  9727. external: null,
  9728. position: 'average',
  9729. backgroundColor: 'rgba(0,0,0,0.8)',
  9730. titleColor: '#fff',
  9731. titleFont: {
  9732. weight: 'bold'
  9733. },
  9734. titleSpacing: 2,
  9735. titleMarginBottom: 6,
  9736. titleAlign: 'left',
  9737. bodyColor: '#fff',
  9738. bodySpacing: 2,
  9739. bodyFont: {},
  9740. bodyAlign: 'left',
  9741. footerColor: '#fff',
  9742. footerSpacing: 2,
  9743. footerMarginTop: 6,
  9744. footerFont: {
  9745. weight: 'bold'
  9746. },
  9747. footerAlign: 'left',
  9748. padding: 6,
  9749. caretPadding: 2,
  9750. caretSize: 5,
  9751. cornerRadius: 6,
  9752. boxHeight: (ctx, opts)=>opts.bodyFont.size,
  9753. boxWidth: (ctx, opts)=>opts.bodyFont.size,
  9754. multiKeyBackground: '#fff',
  9755. displayColors: true,
  9756. boxPadding: 0,
  9757. borderColor: 'rgba(0,0,0,0)',
  9758. borderWidth: 0,
  9759. animation: {
  9760. duration: 400,
  9761. easing: 'easeOutQuart'
  9762. },
  9763. animations: {
  9764. numbers: {
  9765. type: 'number',
  9766. properties: [
  9767. 'x',
  9768. 'y',
  9769. 'width',
  9770. 'height',
  9771. 'caretX',
  9772. 'caretY'
  9773. ]
  9774. },
  9775. opacity: {
  9776. easing: 'linear',
  9777. duration: 200
  9778. }
  9779. },
  9780. callbacks: defaultCallbacks
  9781. },
  9782. defaultRoutes: {
  9783. bodyFont: 'font',
  9784. footerFont: 'font',
  9785. titleFont: 'font'
  9786. },
  9787. descriptors: {
  9788. _scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
  9789. _indexable: false,
  9790. callbacks: {
  9791. _scriptable: false,
  9792. _indexable: false
  9793. },
  9794. animation: {
  9795. _fallback: false
  9796. },
  9797. animations: {
  9798. _fallback: 'animation'
  9799. }
  9800. },
  9801. additionalOptionScopes: [
  9802. 'interaction'
  9803. ]
  9804. };
  9805. var plugins = /*#__PURE__*/Object.freeze({
  9806. __proto__: null,
  9807. Colors: plugin_colors,
  9808. Decimation: plugin_decimation,
  9809. Filler: index,
  9810. Legend: plugin_legend,
  9811. SubTitle: plugin_subtitle,
  9812. Title: plugin_title,
  9813. Tooltip: plugin_tooltip
  9814. });
  9815. const addIfString = (labels, raw, index, addedLabels)=>{
  9816. if (typeof raw === 'string') {
  9817. index = labels.push(raw) - 1;
  9818. addedLabels.unshift({
  9819. index,
  9820. label: raw
  9821. });
  9822. } else if (isNaN(raw)) {
  9823. index = null;
  9824. }
  9825. return index;
  9826. };
  9827. function findOrAddLabel(labels, raw, index, addedLabels) {
  9828. const first = labels.indexOf(raw);
  9829. if (first === -1) {
  9830. return addIfString(labels, raw, index, addedLabels);
  9831. }
  9832. const last = labels.lastIndexOf(raw);
  9833. return first !== last ? index : first;
  9834. }
  9835. const validIndex = (index, max)=>index === null ? null : helpers_segment._limitValue(Math.round(index), 0, max);
  9836. function _getLabelForValue(value) {
  9837. const labels = this.getLabels();
  9838. if (value >= 0 && value < labels.length) {
  9839. return labels[value];
  9840. }
  9841. return value;
  9842. }
  9843. class CategoryScale extends Scale {
  9844. static id = 'category';
  9845. static defaults = {
  9846. ticks: {
  9847. callback: _getLabelForValue
  9848. }
  9849. };
  9850. constructor(cfg){
  9851. super(cfg);
  9852. this._startValue = undefined;
  9853. this._valueRange = 0;
  9854. this._addedLabels = [];
  9855. }
  9856. init(scaleOptions) {
  9857. const added = this._addedLabels;
  9858. if (added.length) {
  9859. const labels = this.getLabels();
  9860. for (const { index , label } of added){
  9861. if (labels[index] === label) {
  9862. labels.splice(index, 1);
  9863. }
  9864. }
  9865. this._addedLabels = [];
  9866. }
  9867. super.init(scaleOptions);
  9868. }
  9869. parse(raw, index) {
  9870. if (helpers_segment.isNullOrUndef(raw)) {
  9871. return null;
  9872. }
  9873. const labels = this.getLabels();
  9874. index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, helpers_segment.valueOrDefault(index, raw), this._addedLabels);
  9875. return validIndex(index, labels.length - 1);
  9876. }
  9877. determineDataLimits() {
  9878. const { minDefined , maxDefined } = this.getUserBounds();
  9879. let { min , max } = this.getMinMax(true);
  9880. if (this.options.bounds === 'ticks') {
  9881. if (!minDefined) {
  9882. min = 0;
  9883. }
  9884. if (!maxDefined) {
  9885. max = this.getLabels().length - 1;
  9886. }
  9887. }
  9888. this.min = min;
  9889. this.max = max;
  9890. }
  9891. buildTicks() {
  9892. const min = this.min;
  9893. const max = this.max;
  9894. const offset = this.options.offset;
  9895. const ticks = [];
  9896. let labels = this.getLabels();
  9897. labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
  9898. this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
  9899. this._startValue = this.min - (offset ? 0.5 : 0);
  9900. for(let value = min; value <= max; value++){
  9901. ticks.push({
  9902. value
  9903. });
  9904. }
  9905. return ticks;
  9906. }
  9907. getLabelForValue(value) {
  9908. return _getLabelForValue.call(this, value);
  9909. }
  9910. configure() {
  9911. super.configure();
  9912. if (!this.isHorizontal()) {
  9913. this._reversePixels = !this._reversePixels;
  9914. }
  9915. }
  9916. getPixelForValue(value) {
  9917. if (typeof value !== 'number') {
  9918. value = this.parse(value);
  9919. }
  9920. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  9921. }
  9922. getPixelForTick(index) {
  9923. const ticks = this.ticks;
  9924. if (index < 0 || index > ticks.length - 1) {
  9925. return null;
  9926. }
  9927. return this.getPixelForValue(ticks[index].value);
  9928. }
  9929. getValueForPixel(pixel) {
  9930. return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
  9931. }
  9932. getBasePixel() {
  9933. return this.bottom;
  9934. }
  9935. }
  9936. function generateTicks$1(generationOptions, dataRange) {
  9937. const ticks = [];
  9938. const MIN_SPACING = 1e-14;
  9939. const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
  9940. const unit = step || 1;
  9941. const maxSpaces = maxTicks - 1;
  9942. const { min: rmin , max: rmax } = dataRange;
  9943. const minDefined = !helpers_segment.isNullOrUndef(min);
  9944. const maxDefined = !helpers_segment.isNullOrUndef(max);
  9945. const countDefined = !helpers_segment.isNullOrUndef(count);
  9946. const minSpacing = (rmax - rmin) / (maxDigits + 1);
  9947. let spacing = helpers_segment.niceNum((rmax - rmin) / maxSpaces / unit) * unit;
  9948. let factor, niceMin, niceMax, numSpaces;
  9949. if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
  9950. return [
  9951. {
  9952. value: rmin
  9953. },
  9954. {
  9955. value: rmax
  9956. }
  9957. ];
  9958. }
  9959. numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
  9960. if (numSpaces > maxSpaces) {
  9961. spacing = helpers_segment.niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
  9962. }
  9963. if (!helpers_segment.isNullOrUndef(precision)) {
  9964. factor = Math.pow(10, precision);
  9965. spacing = Math.ceil(spacing * factor) / factor;
  9966. }
  9967. if (bounds === 'ticks') {
  9968. niceMin = Math.floor(rmin / spacing) * spacing;
  9969. niceMax = Math.ceil(rmax / spacing) * spacing;
  9970. } else {
  9971. niceMin = rmin;
  9972. niceMax = rmax;
  9973. }
  9974. if (minDefined && maxDefined && step && helpers_segment.almostWhole((max - min) / step, spacing / 1000)) {
  9975. numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
  9976. spacing = (max - min) / numSpaces;
  9977. niceMin = min;
  9978. niceMax = max;
  9979. } else if (countDefined) {
  9980. niceMin = minDefined ? min : niceMin;
  9981. niceMax = maxDefined ? max : niceMax;
  9982. numSpaces = count - 1;
  9983. spacing = (niceMax - niceMin) / numSpaces;
  9984. } else {
  9985. numSpaces = (niceMax - niceMin) / spacing;
  9986. if (helpers_segment.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  9987. numSpaces = Math.round(numSpaces);
  9988. } else {
  9989. numSpaces = Math.ceil(numSpaces);
  9990. }
  9991. }
  9992. const decimalPlaces = Math.max(helpers_segment._decimalPlaces(spacing), helpers_segment._decimalPlaces(niceMin));
  9993. factor = Math.pow(10, helpers_segment.isNullOrUndef(precision) ? decimalPlaces : precision);
  9994. niceMin = Math.round(niceMin * factor) / factor;
  9995. niceMax = Math.round(niceMax * factor) / factor;
  9996. let j = 0;
  9997. if (minDefined) {
  9998. if (includeBounds && niceMin !== min) {
  9999. ticks.push({
  10000. value: min
  10001. });
  10002. if (niceMin < min) {
  10003. j++;
  10004. }
  10005. if (helpers_segment.almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
  10006. j++;
  10007. }
  10008. } else if (niceMin < min) {
  10009. j++;
  10010. }
  10011. }
  10012. for(; j < numSpaces; ++j){
  10013. const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
  10014. if (maxDefined && tickValue > max) {
  10015. break;
  10016. }
  10017. ticks.push({
  10018. value: tickValue
  10019. });
  10020. }
  10021. if (maxDefined && includeBounds && niceMax !== max) {
  10022. if (ticks.length && helpers_segment.almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
  10023. ticks[ticks.length - 1].value = max;
  10024. } else {
  10025. ticks.push({
  10026. value: max
  10027. });
  10028. }
  10029. } else if (!maxDefined || niceMax === max) {
  10030. ticks.push({
  10031. value: niceMax
  10032. });
  10033. }
  10034. return ticks;
  10035. }
  10036. function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
  10037. const rad = helpers_segment.toRadians(minRotation);
  10038. const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
  10039. const length = 0.75 * minSpacing * ('' + value).length;
  10040. return Math.min(minSpacing / ratio, length);
  10041. }
  10042. class LinearScaleBase extends Scale {
  10043. constructor(cfg){
  10044. super(cfg);
  10045. this.start = undefined;
  10046. this.end = undefined;
  10047. this._startValue = undefined;
  10048. this._endValue = undefined;
  10049. this._valueRange = 0;
  10050. }
  10051. parse(raw, index) {
  10052. if (helpers_segment.isNullOrUndef(raw)) {
  10053. return null;
  10054. }
  10055. if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
  10056. return null;
  10057. }
  10058. return +raw;
  10059. }
  10060. handleTickRangeOptions() {
  10061. const { beginAtZero } = this.options;
  10062. const { minDefined , maxDefined } = this.getUserBounds();
  10063. let { min , max } = this;
  10064. const setMin = (v)=>min = minDefined ? min : v;
  10065. const setMax = (v)=>max = maxDefined ? max : v;
  10066. if (beginAtZero) {
  10067. const minSign = helpers_segment.sign(min);
  10068. const maxSign = helpers_segment.sign(max);
  10069. if (minSign < 0 && maxSign < 0) {
  10070. setMax(0);
  10071. } else if (minSign > 0 && maxSign > 0) {
  10072. setMin(0);
  10073. }
  10074. }
  10075. if (min === max) {
  10076. let offset = max === 0 ? 1 : Math.abs(max * 0.05);
  10077. setMax(max + offset);
  10078. if (!beginAtZero) {
  10079. setMin(min - offset);
  10080. }
  10081. }
  10082. this.min = min;
  10083. this.max = max;
  10084. }
  10085. getTickLimit() {
  10086. const tickOpts = this.options.ticks;
  10087. let { maxTicksLimit , stepSize } = tickOpts;
  10088. let maxTicks;
  10089. if (stepSize) {
  10090. maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
  10091. if (maxTicks > 1000) {
  10092. console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
  10093. maxTicks = 1000;
  10094. }
  10095. } else {
  10096. maxTicks = this.computeTickLimit();
  10097. maxTicksLimit = maxTicksLimit || 11;
  10098. }
  10099. if (maxTicksLimit) {
  10100. maxTicks = Math.min(maxTicksLimit, maxTicks);
  10101. }
  10102. return maxTicks;
  10103. }
  10104. computeTickLimit() {
  10105. return Number.POSITIVE_INFINITY;
  10106. }
  10107. buildTicks() {
  10108. const opts = this.options;
  10109. const tickOpts = opts.ticks;
  10110. let maxTicks = this.getTickLimit();
  10111. maxTicks = Math.max(2, maxTicks);
  10112. const numericGeneratorOptions = {
  10113. maxTicks,
  10114. bounds: opts.bounds,
  10115. min: opts.min,
  10116. max: opts.max,
  10117. precision: tickOpts.precision,
  10118. step: tickOpts.stepSize,
  10119. count: tickOpts.count,
  10120. maxDigits: this._maxDigits(),
  10121. horizontal: this.isHorizontal(),
  10122. minRotation: tickOpts.minRotation || 0,
  10123. includeBounds: tickOpts.includeBounds !== false
  10124. };
  10125. const dataRange = this._range || this;
  10126. const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
  10127. if (opts.bounds === 'ticks') {
  10128. helpers_segment._setMinAndMaxByKey(ticks, this, 'value');
  10129. }
  10130. if (opts.reverse) {
  10131. ticks.reverse();
  10132. this.start = this.max;
  10133. this.end = this.min;
  10134. } else {
  10135. this.start = this.min;
  10136. this.end = this.max;
  10137. }
  10138. return ticks;
  10139. }
  10140. configure() {
  10141. const ticks = this.ticks;
  10142. let start = this.min;
  10143. let end = this.max;
  10144. super.configure();
  10145. if (this.options.offset && ticks.length) {
  10146. const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
  10147. start -= offset;
  10148. end += offset;
  10149. }
  10150. this._startValue = start;
  10151. this._endValue = end;
  10152. this._valueRange = end - start;
  10153. }
  10154. getLabelForValue(value) {
  10155. return helpers_segment.formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10156. }
  10157. }
  10158. class LinearScale extends LinearScaleBase {
  10159. static id = 'linear';
  10160. static defaults = {
  10161. ticks: {
  10162. callback: helpers_segment.Ticks.formatters.numeric
  10163. }
  10164. };
  10165. determineDataLimits() {
  10166. const { min , max } = this.getMinMax(true);
  10167. this.min = helpers_segment.isNumberFinite(min) ? min : 0;
  10168. this.max = helpers_segment.isNumberFinite(max) ? max : 1;
  10169. this.handleTickRangeOptions();
  10170. }
  10171. computeTickLimit() {
  10172. const horizontal = this.isHorizontal();
  10173. const length = horizontal ? this.width : this.height;
  10174. const minRotation = helpers_segment.toRadians(this.options.ticks.minRotation);
  10175. const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
  10176. const tickFont = this._resolveTickFontOptions(0);
  10177. return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
  10178. }
  10179. getPixelForValue(value) {
  10180. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  10181. }
  10182. getValueForPixel(pixel) {
  10183. return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
  10184. }
  10185. }
  10186. const log10Floor = (v)=>Math.floor(helpers_segment.log10(v));
  10187. const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
  10188. function isMajor(tickVal) {
  10189. const remain = tickVal / Math.pow(10, log10Floor(tickVal));
  10190. return remain === 1;
  10191. }
  10192. function steps(min, max, rangeExp) {
  10193. const rangeStep = Math.pow(10, rangeExp);
  10194. const start = Math.floor(min / rangeStep);
  10195. const end = Math.ceil(max / rangeStep);
  10196. return end - start;
  10197. }
  10198. function startExp(min, max) {
  10199. const range = max - min;
  10200. let rangeExp = log10Floor(range);
  10201. while(steps(min, max, rangeExp) > 10){
  10202. rangeExp++;
  10203. }
  10204. while(steps(min, max, rangeExp) < 10){
  10205. rangeExp--;
  10206. }
  10207. return Math.min(rangeExp, log10Floor(min));
  10208. }
  10209. function generateTicks(generationOptions, { min , max }) {
  10210. min = helpers_segment.finiteOrDefault(generationOptions.min, min);
  10211. const ticks = [];
  10212. const minExp = log10Floor(min);
  10213. let exp = startExp(min, max);
  10214. let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
  10215. const stepSize = Math.pow(10, exp);
  10216. const base = minExp > exp ? Math.pow(10, minExp) : 0;
  10217. const start = Math.round((min - base) * precision) / precision;
  10218. const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
  10219. let significand = Math.floor((start - offset) / Math.pow(10, exp));
  10220. let value = helpers_segment.finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
  10221. while(value < max){
  10222. ticks.push({
  10223. value,
  10224. major: isMajor(value),
  10225. significand
  10226. });
  10227. if (significand >= 10) {
  10228. significand = significand < 15 ? 15 : 20;
  10229. } else {
  10230. significand++;
  10231. }
  10232. if (significand >= 20) {
  10233. exp++;
  10234. significand = 2;
  10235. precision = exp >= 0 ? 1 : precision;
  10236. }
  10237. value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
  10238. }
  10239. const lastTick = helpers_segment.finiteOrDefault(generationOptions.max, value);
  10240. ticks.push({
  10241. value: lastTick,
  10242. major: isMajor(lastTick),
  10243. significand
  10244. });
  10245. return ticks;
  10246. }
  10247. class LogarithmicScale extends Scale {
  10248. static id = 'logarithmic';
  10249. static defaults = {
  10250. ticks: {
  10251. callback: helpers_segment.Ticks.formatters.logarithmic,
  10252. major: {
  10253. enabled: true
  10254. }
  10255. }
  10256. };
  10257. constructor(cfg){
  10258. super(cfg);
  10259. this.start = undefined;
  10260. this.end = undefined;
  10261. this._startValue = undefined;
  10262. this._valueRange = 0;
  10263. }
  10264. parse(raw, index) {
  10265. const value = LinearScaleBase.prototype.parse.apply(this, [
  10266. raw,
  10267. index
  10268. ]);
  10269. if (value === 0) {
  10270. this._zero = true;
  10271. return undefined;
  10272. }
  10273. return helpers_segment.isNumberFinite(value) && value > 0 ? value : null;
  10274. }
  10275. determineDataLimits() {
  10276. const { min , max } = this.getMinMax(true);
  10277. this.min = helpers_segment.isNumberFinite(min) ? Math.max(0, min) : null;
  10278. this.max = helpers_segment.isNumberFinite(max) ? Math.max(0, max) : null;
  10279. if (this.options.beginAtZero) {
  10280. this._zero = true;
  10281. }
  10282. if (this._zero && this.min !== this._suggestedMin && !helpers_segment.isNumberFinite(this._userMin)) {
  10283. this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
  10284. }
  10285. this.handleTickRangeOptions();
  10286. }
  10287. handleTickRangeOptions() {
  10288. const { minDefined , maxDefined } = this.getUserBounds();
  10289. let min = this.min;
  10290. let max = this.max;
  10291. const setMin = (v)=>min = minDefined ? min : v;
  10292. const setMax = (v)=>max = maxDefined ? max : v;
  10293. if (min === max) {
  10294. if (min <= 0) {
  10295. setMin(1);
  10296. setMax(10);
  10297. } else {
  10298. setMin(changeExponent(min, -1));
  10299. setMax(changeExponent(max, +1));
  10300. }
  10301. }
  10302. if (min <= 0) {
  10303. setMin(changeExponent(max, -1));
  10304. }
  10305. if (max <= 0) {
  10306. setMax(changeExponent(min, +1));
  10307. }
  10308. this.min = min;
  10309. this.max = max;
  10310. }
  10311. buildTicks() {
  10312. const opts = this.options;
  10313. const generationOptions = {
  10314. min: this._userMin,
  10315. max: this._userMax
  10316. };
  10317. const ticks = generateTicks(generationOptions, this);
  10318. if (opts.bounds === 'ticks') {
  10319. helpers_segment._setMinAndMaxByKey(ticks, this, 'value');
  10320. }
  10321. if (opts.reverse) {
  10322. ticks.reverse();
  10323. this.start = this.max;
  10324. this.end = this.min;
  10325. } else {
  10326. this.start = this.min;
  10327. this.end = this.max;
  10328. }
  10329. return ticks;
  10330. }
  10331. getLabelForValue(value) {
  10332. return value === undefined ? '0' : helpers_segment.formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  10333. }
  10334. configure() {
  10335. const start = this.min;
  10336. super.configure();
  10337. this._startValue = helpers_segment.log10(start);
  10338. this._valueRange = helpers_segment.log10(this.max) - helpers_segment.log10(start);
  10339. }
  10340. getPixelForValue(value) {
  10341. if (value === undefined || value === 0) {
  10342. value = this.min;
  10343. }
  10344. if (value === null || isNaN(value)) {
  10345. return NaN;
  10346. }
  10347. return this.getPixelForDecimal(value === this.min ? 0 : (helpers_segment.log10(value) - this._startValue) / this._valueRange);
  10348. }
  10349. getValueForPixel(pixel) {
  10350. const decimal = this.getDecimalForPixel(pixel);
  10351. return Math.pow(10, this._startValue + decimal * this._valueRange);
  10352. }
  10353. }
  10354. function getTickBackdropHeight(opts) {
  10355. const tickOpts = opts.ticks;
  10356. if (tickOpts.display && opts.display) {
  10357. const padding = helpers_segment.toPadding(tickOpts.backdropPadding);
  10358. return helpers_segment.valueOrDefault(tickOpts.font && tickOpts.font.size, helpers_segment.defaults.font.size) + padding.height;
  10359. }
  10360. return 0;
  10361. }
  10362. function measureLabelSize(ctx, font, label) {
  10363. label = helpers_segment.isArray(label) ? label : [
  10364. label
  10365. ];
  10366. return {
  10367. w: helpers_segment._longestText(ctx, font.string, label),
  10368. h: label.length * font.lineHeight
  10369. };
  10370. }
  10371. function determineLimits(angle, pos, size, min, max) {
  10372. if (angle === min || angle === max) {
  10373. return {
  10374. start: pos - size / 2,
  10375. end: pos + size / 2
  10376. };
  10377. } else if (angle < min || angle > max) {
  10378. return {
  10379. start: pos - size,
  10380. end: pos
  10381. };
  10382. }
  10383. return {
  10384. start: pos,
  10385. end: pos + size
  10386. };
  10387. }
  10388. function fitWithPointLabels(scale) {
  10389. const orig = {
  10390. l: scale.left + scale._padding.left,
  10391. r: scale.right - scale._padding.right,
  10392. t: scale.top + scale._padding.top,
  10393. b: scale.bottom - scale._padding.bottom
  10394. };
  10395. const limits = Object.assign({}, orig);
  10396. const labelSizes = [];
  10397. const padding = [];
  10398. const valueCount = scale._pointLabels.length;
  10399. const pointLabelOpts = scale.options.pointLabels;
  10400. const additionalAngle = pointLabelOpts.centerPointLabels ? helpers_segment.PI / valueCount : 0;
  10401. for(let i = 0; i < valueCount; i++){
  10402. const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
  10403. padding[i] = opts.padding;
  10404. const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
  10405. const plFont = helpers_segment.toFont(opts.font);
  10406. const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
  10407. labelSizes[i] = textSize;
  10408. const angleRadians = helpers_segment._normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
  10409. const angle = Math.round(helpers_segment.toDegrees(angleRadians));
  10410. const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  10411. const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  10412. updateLimits(limits, orig, angleRadians, hLimits, vLimits);
  10413. }
  10414. scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
  10415. scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  10416. }
  10417. function updateLimits(limits, orig, angle, hLimits, vLimits) {
  10418. const sin = Math.abs(Math.sin(angle));
  10419. const cos = Math.abs(Math.cos(angle));
  10420. let x = 0;
  10421. let y = 0;
  10422. if (hLimits.start < orig.l) {
  10423. x = (orig.l - hLimits.start) / sin;
  10424. limits.l = Math.min(limits.l, orig.l - x);
  10425. } else if (hLimits.end > orig.r) {
  10426. x = (hLimits.end - orig.r) / sin;
  10427. limits.r = Math.max(limits.r, orig.r + x);
  10428. }
  10429. if (vLimits.start < orig.t) {
  10430. y = (orig.t - vLimits.start) / cos;
  10431. limits.t = Math.min(limits.t, orig.t - y);
  10432. } else if (vLimits.end > orig.b) {
  10433. y = (vLimits.end - orig.b) / cos;
  10434. limits.b = Math.max(limits.b, orig.b + y);
  10435. }
  10436. }
  10437. function createPointLabelItem(scale, index, itemOpts) {
  10438. const outerDistance = scale.drawingArea;
  10439. const { extra , additionalAngle , padding , size } = itemOpts;
  10440. const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
  10441. const angle = Math.round(helpers_segment.toDegrees(helpers_segment._normalizeAngle(pointLabelPosition.angle + helpers_segment.HALF_PI)));
  10442. const y = yForAngle(pointLabelPosition.y, size.h, angle);
  10443. const textAlign = getTextAlignForAngle(angle);
  10444. const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
  10445. return {
  10446. visible: true,
  10447. x: pointLabelPosition.x,
  10448. y,
  10449. textAlign,
  10450. left,
  10451. top: y,
  10452. right: left + size.w,
  10453. bottom: y + size.h
  10454. };
  10455. }
  10456. function isNotOverlapped(item, area) {
  10457. if (!area) {
  10458. return true;
  10459. }
  10460. const { left , top , right , bottom } = item;
  10461. const apexesInArea = helpers_segment._isPointInArea({
  10462. x: left,
  10463. y: top
  10464. }, area) || helpers_segment._isPointInArea({
  10465. x: left,
  10466. y: bottom
  10467. }, area) || helpers_segment._isPointInArea({
  10468. x: right,
  10469. y: top
  10470. }, area) || helpers_segment._isPointInArea({
  10471. x: right,
  10472. y: bottom
  10473. }, area);
  10474. return !apexesInArea;
  10475. }
  10476. function buildPointLabelItems(scale, labelSizes, padding) {
  10477. const items = [];
  10478. const valueCount = scale._pointLabels.length;
  10479. const opts = scale.options;
  10480. const { centerPointLabels , display } = opts.pointLabels;
  10481. const itemOpts = {
  10482. extra: getTickBackdropHeight(opts) / 2,
  10483. additionalAngle: centerPointLabels ? helpers_segment.PI / valueCount : 0
  10484. };
  10485. let area;
  10486. for(let i = 0; i < valueCount; i++){
  10487. itemOpts.padding = padding[i];
  10488. itemOpts.size = labelSizes[i];
  10489. const item = createPointLabelItem(scale, i, itemOpts);
  10490. items.push(item);
  10491. if (display === 'auto') {
  10492. item.visible = isNotOverlapped(item, area);
  10493. if (item.visible) {
  10494. area = item;
  10495. }
  10496. }
  10497. }
  10498. return items;
  10499. }
  10500. function getTextAlignForAngle(angle) {
  10501. if (angle === 0 || angle === 180) {
  10502. return 'center';
  10503. } else if (angle < 180) {
  10504. return 'left';
  10505. }
  10506. return 'right';
  10507. }
  10508. function leftForTextAlign(x, w, align) {
  10509. if (align === 'right') {
  10510. x -= w;
  10511. } else if (align === 'center') {
  10512. x -= w / 2;
  10513. }
  10514. return x;
  10515. }
  10516. function yForAngle(y, h, angle) {
  10517. if (angle === 90 || angle === 270) {
  10518. y -= h / 2;
  10519. } else if (angle > 270 || angle < 90) {
  10520. y -= h;
  10521. }
  10522. return y;
  10523. }
  10524. function drawPointLabelBox(ctx, opts, item) {
  10525. const { left , top , right , bottom } = item;
  10526. const { backdropColor } = opts;
  10527. if (!helpers_segment.isNullOrUndef(backdropColor)) {
  10528. const borderRadius = helpers_segment.toTRBLCorners(opts.borderRadius);
  10529. const padding = helpers_segment.toPadding(opts.backdropPadding);
  10530. ctx.fillStyle = backdropColor;
  10531. const backdropLeft = left - padding.left;
  10532. const backdropTop = top - padding.top;
  10533. const backdropWidth = right - left + padding.width;
  10534. const backdropHeight = bottom - top + padding.height;
  10535. if (Object.values(borderRadius).some((v)=>v !== 0)) {
  10536. ctx.beginPath();
  10537. helpers_segment.addRoundedRectPath(ctx, {
  10538. x: backdropLeft,
  10539. y: backdropTop,
  10540. w: backdropWidth,
  10541. h: backdropHeight,
  10542. radius: borderRadius
  10543. });
  10544. ctx.fill();
  10545. } else {
  10546. ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
  10547. }
  10548. }
  10549. }
  10550. function drawPointLabels(scale, labelCount) {
  10551. const { ctx , options: { pointLabels } } = scale;
  10552. for(let i = labelCount - 1; i >= 0; i--){
  10553. const item = scale._pointLabelItems[i];
  10554. if (!item.visible) {
  10555. continue;
  10556. }
  10557. const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
  10558. drawPointLabelBox(ctx, optsAtIndex, item);
  10559. const plFont = helpers_segment.toFont(optsAtIndex.font);
  10560. const { x , y , textAlign } = item;
  10561. helpers_segment.renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
  10562. color: optsAtIndex.color,
  10563. textAlign: textAlign,
  10564. textBaseline: 'middle'
  10565. });
  10566. }
  10567. }
  10568. function pathRadiusLine(scale, radius, circular, labelCount) {
  10569. const { ctx } = scale;
  10570. if (circular) {
  10571. ctx.arc(scale.xCenter, scale.yCenter, radius, 0, helpers_segment.TAU);
  10572. } else {
  10573. let pointPosition = scale.getPointPosition(0, radius);
  10574. ctx.moveTo(pointPosition.x, pointPosition.y);
  10575. for(let i = 1; i < labelCount; i++){
  10576. pointPosition = scale.getPointPosition(i, radius);
  10577. ctx.lineTo(pointPosition.x, pointPosition.y);
  10578. }
  10579. }
  10580. }
  10581. function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
  10582. const ctx = scale.ctx;
  10583. const circular = gridLineOpts.circular;
  10584. const { color , lineWidth } = gridLineOpts;
  10585. if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
  10586. return;
  10587. }
  10588. ctx.save();
  10589. ctx.strokeStyle = color;
  10590. ctx.lineWidth = lineWidth;
  10591. ctx.setLineDash(borderOpts.dash);
  10592. ctx.lineDashOffset = borderOpts.dashOffset;
  10593. ctx.beginPath();
  10594. pathRadiusLine(scale, radius, circular, labelCount);
  10595. ctx.closePath();
  10596. ctx.stroke();
  10597. ctx.restore();
  10598. }
  10599. function createPointLabelContext(parent, index, label) {
  10600. return helpers_segment.createContext(parent, {
  10601. label,
  10602. index,
  10603. type: 'pointLabel'
  10604. });
  10605. }
  10606. class RadialLinearScale extends LinearScaleBase {
  10607. static id = 'radialLinear';
  10608. static defaults = {
  10609. display: true,
  10610. animate: true,
  10611. position: 'chartArea',
  10612. angleLines: {
  10613. display: true,
  10614. lineWidth: 1,
  10615. borderDash: [],
  10616. borderDashOffset: 0.0
  10617. },
  10618. grid: {
  10619. circular: false
  10620. },
  10621. startAngle: 0,
  10622. ticks: {
  10623. showLabelBackdrop: true,
  10624. callback: helpers_segment.Ticks.formatters.numeric
  10625. },
  10626. pointLabels: {
  10627. backdropColor: undefined,
  10628. backdropPadding: 2,
  10629. display: true,
  10630. font: {
  10631. size: 10
  10632. },
  10633. callback (label) {
  10634. return label;
  10635. },
  10636. padding: 5,
  10637. centerPointLabels: false
  10638. }
  10639. };
  10640. static defaultRoutes = {
  10641. 'angleLines.color': 'borderColor',
  10642. 'pointLabels.color': 'color',
  10643. 'ticks.color': 'color'
  10644. };
  10645. static descriptors = {
  10646. angleLines: {
  10647. _fallback: 'grid'
  10648. }
  10649. };
  10650. constructor(cfg){
  10651. super(cfg);
  10652. this.xCenter = undefined;
  10653. this.yCenter = undefined;
  10654. this.drawingArea = undefined;
  10655. this._pointLabels = [];
  10656. this._pointLabelItems = [];
  10657. }
  10658. setDimensions() {
  10659. const padding = this._padding = helpers_segment.toPadding(getTickBackdropHeight(this.options) / 2);
  10660. const w = this.width = this.maxWidth - padding.width;
  10661. const h = this.height = this.maxHeight - padding.height;
  10662. this.xCenter = Math.floor(this.left + w / 2 + padding.left);
  10663. this.yCenter = Math.floor(this.top + h / 2 + padding.top);
  10664. this.drawingArea = Math.floor(Math.min(w, h) / 2);
  10665. }
  10666. determineDataLimits() {
  10667. const { min , max } = this.getMinMax(false);
  10668. this.min = helpers_segment.isNumberFinite(min) && !isNaN(min) ? min : 0;
  10669. this.max = helpers_segment.isNumberFinite(max) && !isNaN(max) ? max : 0;
  10670. this.handleTickRangeOptions();
  10671. }
  10672. computeTickLimit() {
  10673. return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  10674. }
  10675. generateTickLabels(ticks) {
  10676. LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
  10677. this._pointLabels = this.getLabels().map((value, index)=>{
  10678. const label = helpers_segment.callback(this.options.pointLabels.callback, [
  10679. value,
  10680. index
  10681. ], this);
  10682. return label || label === 0 ? label : '';
  10683. }).filter((v, i)=>this.chart.getDataVisibility(i));
  10684. }
  10685. fit() {
  10686. const opts = this.options;
  10687. if (opts.display && opts.pointLabels.display) {
  10688. fitWithPointLabels(this);
  10689. } else {
  10690. this.setCenterPoint(0, 0, 0, 0);
  10691. }
  10692. }
  10693. setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
  10694. this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
  10695. this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
  10696. this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
  10697. }
  10698. getIndexAngle(index) {
  10699. const angleMultiplier = helpers_segment.TAU / (this._pointLabels.length || 1);
  10700. const startAngle = this.options.startAngle || 0;
  10701. return helpers_segment._normalizeAngle(index * angleMultiplier + helpers_segment.toRadians(startAngle));
  10702. }
  10703. getDistanceFromCenterForValue(value) {
  10704. if (helpers_segment.isNullOrUndef(value)) {
  10705. return NaN;
  10706. }
  10707. const scalingFactor = this.drawingArea / (this.max - this.min);
  10708. if (this.options.reverse) {
  10709. return (this.max - value) * scalingFactor;
  10710. }
  10711. return (value - this.min) * scalingFactor;
  10712. }
  10713. getValueForDistanceFromCenter(distance) {
  10714. if (helpers_segment.isNullOrUndef(distance)) {
  10715. return NaN;
  10716. }
  10717. const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
  10718. return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
  10719. }
  10720. getPointLabelContext(index) {
  10721. const pointLabels = this._pointLabels || [];
  10722. if (index >= 0 && index < pointLabels.length) {
  10723. const pointLabel = pointLabels[index];
  10724. return createPointLabelContext(this.getContext(), index, pointLabel);
  10725. }
  10726. }
  10727. getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
  10728. const angle = this.getIndexAngle(index) - helpers_segment.HALF_PI + additionalAngle;
  10729. return {
  10730. x: Math.cos(angle) * distanceFromCenter + this.xCenter,
  10731. y: Math.sin(angle) * distanceFromCenter + this.yCenter,
  10732. angle
  10733. };
  10734. }
  10735. getPointPositionForValue(index, value) {
  10736. return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  10737. }
  10738. getBasePosition(index) {
  10739. return this.getPointPositionForValue(index || 0, this.getBaseValue());
  10740. }
  10741. getPointLabelPosition(index) {
  10742. const { left , top , right , bottom } = this._pointLabelItems[index];
  10743. return {
  10744. left,
  10745. top,
  10746. right,
  10747. bottom
  10748. };
  10749. }
  10750. drawBackground() {
  10751. const { backgroundColor , grid: { circular } } = this.options;
  10752. if (backgroundColor) {
  10753. const ctx = this.ctx;
  10754. ctx.save();
  10755. ctx.beginPath();
  10756. pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
  10757. ctx.closePath();
  10758. ctx.fillStyle = backgroundColor;
  10759. ctx.fill();
  10760. ctx.restore();
  10761. }
  10762. }
  10763. drawGrid() {
  10764. const ctx = this.ctx;
  10765. const opts = this.options;
  10766. const { angleLines , grid , border } = opts;
  10767. const labelCount = this._pointLabels.length;
  10768. let i, offset, position;
  10769. if (opts.pointLabels.display) {
  10770. drawPointLabels(this, labelCount);
  10771. }
  10772. if (grid.display) {
  10773. this.ticks.forEach((tick, index)=>{
  10774. if (index !== 0) {
  10775. offset = this.getDistanceFromCenterForValue(tick.value);
  10776. const context = this.getContext(index);
  10777. const optsAtIndex = grid.setContext(context);
  10778. const optsAtIndexBorder = border.setContext(context);
  10779. drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
  10780. }
  10781. });
  10782. }
  10783. if (angleLines.display) {
  10784. ctx.save();
  10785. for(i = labelCount - 1; i >= 0; i--){
  10786. const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
  10787. const { color , lineWidth } = optsAtIndex;
  10788. if (!lineWidth || !color) {
  10789. continue;
  10790. }
  10791. ctx.lineWidth = lineWidth;
  10792. ctx.strokeStyle = color;
  10793. ctx.setLineDash(optsAtIndex.borderDash);
  10794. ctx.lineDashOffset = optsAtIndex.borderDashOffset;
  10795. offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
  10796. position = this.getPointPosition(i, offset);
  10797. ctx.beginPath();
  10798. ctx.moveTo(this.xCenter, this.yCenter);
  10799. ctx.lineTo(position.x, position.y);
  10800. ctx.stroke();
  10801. }
  10802. ctx.restore();
  10803. }
  10804. }
  10805. drawBorder() {}
  10806. drawLabels() {
  10807. const ctx = this.ctx;
  10808. const opts = this.options;
  10809. const tickOpts = opts.ticks;
  10810. if (!tickOpts.display) {
  10811. return;
  10812. }
  10813. const startAngle = this.getIndexAngle(0);
  10814. let offset, width;
  10815. ctx.save();
  10816. ctx.translate(this.xCenter, this.yCenter);
  10817. ctx.rotate(startAngle);
  10818. ctx.textAlign = 'center';
  10819. ctx.textBaseline = 'middle';
  10820. this.ticks.forEach((tick, index)=>{
  10821. if (index === 0 && !opts.reverse) {
  10822. return;
  10823. }
  10824. const optsAtIndex = tickOpts.setContext(this.getContext(index));
  10825. const tickFont = helpers_segment.toFont(optsAtIndex.font);
  10826. offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
  10827. if (optsAtIndex.showLabelBackdrop) {
  10828. ctx.font = tickFont.string;
  10829. width = ctx.measureText(tick.label).width;
  10830. ctx.fillStyle = optsAtIndex.backdropColor;
  10831. const padding = helpers_segment.toPadding(optsAtIndex.backdropPadding);
  10832. ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
  10833. }
  10834. helpers_segment.renderText(ctx, tick.label, 0, -offset, tickFont, {
  10835. color: optsAtIndex.color,
  10836. strokeColor: optsAtIndex.textStrokeColor,
  10837. strokeWidth: optsAtIndex.textStrokeWidth
  10838. });
  10839. });
  10840. ctx.restore();
  10841. }
  10842. drawTitle() {}
  10843. }
  10844. const INTERVALS = {
  10845. millisecond: {
  10846. common: true,
  10847. size: 1,
  10848. steps: 1000
  10849. },
  10850. second: {
  10851. common: true,
  10852. size: 1000,
  10853. steps: 60
  10854. },
  10855. minute: {
  10856. common: true,
  10857. size: 60000,
  10858. steps: 60
  10859. },
  10860. hour: {
  10861. common: true,
  10862. size: 3600000,
  10863. steps: 24
  10864. },
  10865. day: {
  10866. common: true,
  10867. size: 86400000,
  10868. steps: 30
  10869. },
  10870. week: {
  10871. common: false,
  10872. size: 604800000,
  10873. steps: 4
  10874. },
  10875. month: {
  10876. common: true,
  10877. size: 2.628e9,
  10878. steps: 12
  10879. },
  10880. quarter: {
  10881. common: false,
  10882. size: 7.884e9,
  10883. steps: 4
  10884. },
  10885. year: {
  10886. common: true,
  10887. size: 3.154e10
  10888. }
  10889. };
  10890. const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
  10891. function sorter(a, b) {
  10892. return a - b;
  10893. }
  10894. function parse(scale, input) {
  10895. if (helpers_segment.isNullOrUndef(input)) {
  10896. return null;
  10897. }
  10898. const adapter = scale._adapter;
  10899. const { parser , round , isoWeekday } = scale._parseOpts;
  10900. let value = input;
  10901. if (typeof parser === 'function') {
  10902. value = parser(value);
  10903. }
  10904. if (!helpers_segment.isNumberFinite(value)) {
  10905. value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
  10906. }
  10907. if (value === null) {
  10908. return null;
  10909. }
  10910. if (round) {
  10911. value = round === 'week' && (helpers_segment.isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
  10912. }
  10913. return +value;
  10914. }
  10915. function determineUnitForAutoTicks(minUnit, min, max, capacity) {
  10916. const ilen = UNITS.length;
  10917. for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
  10918. const interval = INTERVALS[UNITS[i]];
  10919. const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
  10920. if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
  10921. return UNITS[i];
  10922. }
  10923. }
  10924. return UNITS[ilen - 1];
  10925. }
  10926. function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
  10927. for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
  10928. const unit = UNITS[i];
  10929. if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
  10930. return unit;
  10931. }
  10932. }
  10933. return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  10934. }
  10935. function determineMajorUnit(unit) {
  10936. for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
  10937. if (INTERVALS[UNITS[i]].common) {
  10938. return UNITS[i];
  10939. }
  10940. }
  10941. }
  10942. function addTick(ticks, time, timestamps) {
  10943. if (!timestamps) {
  10944. ticks[time] = true;
  10945. } else if (timestamps.length) {
  10946. const { lo , hi } = helpers_segment._lookup(timestamps, time);
  10947. const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
  10948. ticks[timestamp] = true;
  10949. }
  10950. }
  10951. function setMajorTicks(scale, ticks, map, majorUnit) {
  10952. const adapter = scale._adapter;
  10953. const first = +adapter.startOf(ticks[0].value, majorUnit);
  10954. const last = ticks[ticks.length - 1].value;
  10955. let major, index;
  10956. for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
  10957. index = map[major];
  10958. if (index >= 0) {
  10959. ticks[index].major = true;
  10960. }
  10961. }
  10962. return ticks;
  10963. }
  10964. function ticksFromTimestamps(scale, values, majorUnit) {
  10965. const ticks = [];
  10966. const map = {};
  10967. const ilen = values.length;
  10968. let i, value;
  10969. for(i = 0; i < ilen; ++i){
  10970. value = values[i];
  10971. map[value] = i;
  10972. ticks.push({
  10973. value,
  10974. major: false
  10975. });
  10976. }
  10977. return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  10978. }
  10979. class TimeScale extends Scale {
  10980. static id = 'time';
  10981. static defaults = {
  10982. bounds: 'data',
  10983. adapters: {},
  10984. time: {
  10985. parser: false,
  10986. unit: false,
  10987. round: false,
  10988. isoWeekday: false,
  10989. minUnit: 'millisecond',
  10990. displayFormats: {}
  10991. },
  10992. ticks: {
  10993. source: 'auto',
  10994. callback: false,
  10995. major: {
  10996. enabled: false
  10997. }
  10998. }
  10999. };
  11000. constructor(props){
  11001. super(props);
  11002. this._cache = {
  11003. data: [],
  11004. labels: [],
  11005. all: []
  11006. };
  11007. this._unit = 'day';
  11008. this._majorUnit = undefined;
  11009. this._offsets = {};
  11010. this._normalized = false;
  11011. this._parseOpts = undefined;
  11012. }
  11013. init(scaleOpts, opts = {}) {
  11014. const time = scaleOpts.time || (scaleOpts.time = {});
  11015. const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
  11016. adapter.init(opts);
  11017. helpers_segment.mergeIf(time.displayFormats, adapter.formats());
  11018. this._parseOpts = {
  11019. parser: time.parser,
  11020. round: time.round,
  11021. isoWeekday: time.isoWeekday
  11022. };
  11023. super.init(scaleOpts);
  11024. this._normalized = opts.normalized;
  11025. }
  11026. parse(raw, index) {
  11027. if (raw === undefined) {
  11028. return null;
  11029. }
  11030. return parse(this, raw);
  11031. }
  11032. beforeLayout() {
  11033. super.beforeLayout();
  11034. this._cache = {
  11035. data: [],
  11036. labels: [],
  11037. all: []
  11038. };
  11039. }
  11040. determineDataLimits() {
  11041. const options = this.options;
  11042. const adapter = this._adapter;
  11043. const unit = options.time.unit || 'day';
  11044. let { min , max , minDefined , maxDefined } = this.getUserBounds();
  11045. function _applyBounds(bounds) {
  11046. if (!minDefined && !isNaN(bounds.min)) {
  11047. min = Math.min(min, bounds.min);
  11048. }
  11049. if (!maxDefined && !isNaN(bounds.max)) {
  11050. max = Math.max(max, bounds.max);
  11051. }
  11052. }
  11053. if (!minDefined || !maxDefined) {
  11054. _applyBounds(this._getLabelBounds());
  11055. if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
  11056. _applyBounds(this.getMinMax(false));
  11057. }
  11058. }
  11059. min = helpers_segment.isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
  11060. max = helpers_segment.isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
  11061. this.min = Math.min(min, max - 1);
  11062. this.max = Math.max(min + 1, max);
  11063. }
  11064. _getLabelBounds() {
  11065. const arr = this.getLabelTimestamps();
  11066. let min = Number.POSITIVE_INFINITY;
  11067. let max = Number.NEGATIVE_INFINITY;
  11068. if (arr.length) {
  11069. min = arr[0];
  11070. max = arr[arr.length - 1];
  11071. }
  11072. return {
  11073. min,
  11074. max
  11075. };
  11076. }
  11077. buildTicks() {
  11078. const options = this.options;
  11079. const timeOpts = options.time;
  11080. const tickOpts = options.ticks;
  11081. const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
  11082. if (options.bounds === 'ticks' && timestamps.length) {
  11083. this.min = this._userMin || timestamps[0];
  11084. this.max = this._userMax || timestamps[timestamps.length - 1];
  11085. }
  11086. const min = this.min;
  11087. const max = this.max;
  11088. const ticks = helpers_segment._filterBetween(timestamps, min, max);
  11089. 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));
  11090. this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
  11091. this.initOffsets(timestamps);
  11092. if (options.reverse) {
  11093. ticks.reverse();
  11094. }
  11095. return ticksFromTimestamps(this, ticks, this._majorUnit);
  11096. }
  11097. afterAutoSkip() {
  11098. if (this.options.offsetAfterAutoskip) {
  11099. this.initOffsets(this.ticks.map((tick)=>+tick.value));
  11100. }
  11101. }
  11102. initOffsets(timestamps = []) {
  11103. let start = 0;
  11104. let end = 0;
  11105. let first, last;
  11106. if (this.options.offset && timestamps.length) {
  11107. first = this.getDecimalForValue(timestamps[0]);
  11108. if (timestamps.length === 1) {
  11109. start = 1 - first;
  11110. } else {
  11111. start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
  11112. }
  11113. last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
  11114. if (timestamps.length === 1) {
  11115. end = last;
  11116. } else {
  11117. end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
  11118. }
  11119. }
  11120. const limit = timestamps.length < 3 ? 0.5 : 0.25;
  11121. start = helpers_segment._limitValue(start, 0, limit);
  11122. end = helpers_segment._limitValue(end, 0, limit);
  11123. this._offsets = {
  11124. start,
  11125. end,
  11126. factor: 1 / (start + 1 + end)
  11127. };
  11128. }
  11129. _generate() {
  11130. const adapter = this._adapter;
  11131. const min = this.min;
  11132. const max = this.max;
  11133. const options = this.options;
  11134. const timeOpts = options.time;
  11135. const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
  11136. const stepSize = helpers_segment.valueOrDefault(options.ticks.stepSize, 1);
  11137. const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
  11138. const hasWeekday = helpers_segment.isNumber(weekday) || weekday === true;
  11139. const ticks = {};
  11140. let first = min;
  11141. let time, count;
  11142. if (hasWeekday) {
  11143. first = +adapter.startOf(first, 'isoWeek', weekday);
  11144. }
  11145. first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
  11146. if (adapter.diff(max, min, minor) > 100000 * stepSize) {
  11147. throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
  11148. }
  11149. const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
  11150. for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
  11151. addTick(ticks, time, timestamps);
  11152. }
  11153. if (time === max || options.bounds === 'ticks' || count === 1) {
  11154. addTick(ticks, time, timestamps);
  11155. }
  11156. return Object.keys(ticks).sort(sorter).map((x)=>+x);
  11157. }
  11158. getLabelForValue(value) {
  11159. const adapter = this._adapter;
  11160. const timeOpts = this.options.time;
  11161. if (timeOpts.tooltipFormat) {
  11162. return adapter.format(value, timeOpts.tooltipFormat);
  11163. }
  11164. return adapter.format(value, timeOpts.displayFormats.datetime);
  11165. }
  11166. format(value, format) {
  11167. const options = this.options;
  11168. const formats = options.time.displayFormats;
  11169. const unit = this._unit;
  11170. const fmt = format || formats[unit];
  11171. return this._adapter.format(value, fmt);
  11172. }
  11173. _tickFormatFunction(time, index, ticks, format) {
  11174. const options = this.options;
  11175. const formatter = options.ticks.callback;
  11176. if (formatter) {
  11177. return helpers_segment.callback(formatter, [
  11178. time,
  11179. index,
  11180. ticks
  11181. ], this);
  11182. }
  11183. const formats = options.time.displayFormats;
  11184. const unit = this._unit;
  11185. const majorUnit = this._majorUnit;
  11186. const minorFormat = unit && formats[unit];
  11187. const majorFormat = majorUnit && formats[majorUnit];
  11188. const tick = ticks[index];
  11189. const major = majorUnit && majorFormat && tick && tick.major;
  11190. return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
  11191. }
  11192. generateTickLabels(ticks) {
  11193. let i, ilen, tick;
  11194. for(i = 0, ilen = ticks.length; i < ilen; ++i){
  11195. tick = ticks[i];
  11196. tick.label = this._tickFormatFunction(tick.value, i, ticks);
  11197. }
  11198. }
  11199. getDecimalForValue(value) {
  11200. return value === null ? NaN : (value - this.min) / (this.max - this.min);
  11201. }
  11202. getPixelForValue(value) {
  11203. const offsets = this._offsets;
  11204. const pos = this.getDecimalForValue(value);
  11205. return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
  11206. }
  11207. getValueForPixel(pixel) {
  11208. const offsets = this._offsets;
  11209. const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11210. return this.min + pos * (this.max - this.min);
  11211. }
  11212. _getLabelSize(label) {
  11213. const ticksOpts = this.options.ticks;
  11214. const tickLabelWidth = this.ctx.measureText(label).width;
  11215. const angle = helpers_segment.toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
  11216. const cosRotation = Math.cos(angle);
  11217. const sinRotation = Math.sin(angle);
  11218. const tickFontSize = this._resolveTickFontOptions(0).size;
  11219. return {
  11220. w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
  11221. h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
  11222. };
  11223. }
  11224. _getLabelCapacity(exampleTime) {
  11225. const timeOpts = this.options.time;
  11226. const displayFormats = timeOpts.displayFormats;
  11227. const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
  11228. const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
  11229. exampleTime
  11230. ], this._majorUnit), format);
  11231. const size = this._getLabelSize(exampleLabel);
  11232. const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
  11233. return capacity > 0 ? capacity : 1;
  11234. }
  11235. getDataTimestamps() {
  11236. let timestamps = this._cache.data || [];
  11237. let i, ilen;
  11238. if (timestamps.length) {
  11239. return timestamps;
  11240. }
  11241. const metas = this.getMatchingVisibleMetas();
  11242. if (this._normalized && metas.length) {
  11243. return this._cache.data = metas[0].controller.getAllParsedValues(this);
  11244. }
  11245. for(i = 0, ilen = metas.length; i < ilen; ++i){
  11246. timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
  11247. }
  11248. return this._cache.data = this.normalize(timestamps);
  11249. }
  11250. getLabelTimestamps() {
  11251. const timestamps = this._cache.labels || [];
  11252. let i, ilen;
  11253. if (timestamps.length) {
  11254. return timestamps;
  11255. }
  11256. const labels = this.getLabels();
  11257. for(i = 0, ilen = labels.length; i < ilen; ++i){
  11258. timestamps.push(parse(this, labels[i]));
  11259. }
  11260. return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
  11261. }
  11262. normalize(values) {
  11263. return helpers_segment._arrayUnique(values.sort(sorter));
  11264. }
  11265. }
  11266. function interpolate(table, val, reverse) {
  11267. let lo = 0;
  11268. let hi = table.length - 1;
  11269. let prevSource, nextSource, prevTarget, nextTarget;
  11270. if (reverse) {
  11271. if (val >= table[lo].pos && val <= table[hi].pos) {
  11272. ({ lo , hi } = helpers_segment._lookupByKey(table, 'pos', val));
  11273. }
  11274. ({ pos: prevSource , time: prevTarget } = table[lo]);
  11275. ({ pos: nextSource , time: nextTarget } = table[hi]);
  11276. } else {
  11277. if (val >= table[lo].time && val <= table[hi].time) {
  11278. ({ lo , hi } = helpers_segment._lookupByKey(table, 'time', val));
  11279. }
  11280. ({ time: prevSource , pos: prevTarget } = table[lo]);
  11281. ({ time: nextSource , pos: nextTarget } = table[hi]);
  11282. }
  11283. const span = nextSource - prevSource;
  11284. return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  11285. }
  11286. class TimeSeriesScale extends TimeScale {
  11287. static id = 'timeseries';
  11288. static defaults = TimeScale.defaults;
  11289. constructor(props){
  11290. super(props);
  11291. this._table = [];
  11292. this._minPos = undefined;
  11293. this._tableRange = undefined;
  11294. }
  11295. initOffsets() {
  11296. const timestamps = this._getTimestampsForTable();
  11297. const table = this._table = this.buildLookupTable(timestamps);
  11298. this._minPos = interpolate(table, this.min);
  11299. this._tableRange = interpolate(table, this.max) - this._minPos;
  11300. super.initOffsets(timestamps);
  11301. }
  11302. buildLookupTable(timestamps) {
  11303. const { min , max } = this;
  11304. const items = [];
  11305. const table = [];
  11306. let i, ilen, prev, curr, next;
  11307. for(i = 0, ilen = timestamps.length; i < ilen; ++i){
  11308. curr = timestamps[i];
  11309. if (curr >= min && curr <= max) {
  11310. items.push(curr);
  11311. }
  11312. }
  11313. if (items.length < 2) {
  11314. return [
  11315. {
  11316. time: min,
  11317. pos: 0
  11318. },
  11319. {
  11320. time: max,
  11321. pos: 1
  11322. }
  11323. ];
  11324. }
  11325. for(i = 0, ilen = items.length; i < ilen; ++i){
  11326. next = items[i + 1];
  11327. prev = items[i - 1];
  11328. curr = items[i];
  11329. if (Math.round((next + prev) / 2) !== curr) {
  11330. table.push({
  11331. time: curr,
  11332. pos: i / (ilen - 1)
  11333. });
  11334. }
  11335. }
  11336. return table;
  11337. }
  11338. _generate() {
  11339. const min = this.min;
  11340. const max = this.max;
  11341. let timestamps = super.getDataTimestamps();
  11342. if (!timestamps.includes(min) || !timestamps.length) {
  11343. timestamps.splice(0, 0, min);
  11344. }
  11345. if (!timestamps.includes(max) || timestamps.length === 1) {
  11346. timestamps.push(max);
  11347. }
  11348. return timestamps.sort((a, b)=>a - b);
  11349. }
  11350. _getTimestampsForTable() {
  11351. let timestamps = this._cache.all || [];
  11352. if (timestamps.length) {
  11353. return timestamps;
  11354. }
  11355. const data = this.getDataTimestamps();
  11356. const label = this.getLabelTimestamps();
  11357. if (data.length && label.length) {
  11358. timestamps = this.normalize(data.concat(label));
  11359. } else {
  11360. timestamps = data.length ? data : label;
  11361. }
  11362. timestamps = this._cache.all = timestamps;
  11363. return timestamps;
  11364. }
  11365. getDecimalForValue(value) {
  11366. return (interpolate(this._table, value) - this._minPos) / this._tableRange;
  11367. }
  11368. getValueForPixel(pixel) {
  11369. const offsets = this._offsets;
  11370. const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  11371. return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
  11372. }
  11373. }
  11374. var scales = /*#__PURE__*/Object.freeze({
  11375. __proto__: null,
  11376. CategoryScale: CategoryScale,
  11377. LinearScale: LinearScale,
  11378. LogarithmicScale: LogarithmicScale,
  11379. RadialLinearScale: RadialLinearScale,
  11380. TimeScale: TimeScale,
  11381. TimeSeriesScale: TimeSeriesScale
  11382. });
  11383. const registerables = [
  11384. controllers,
  11385. elements,
  11386. plugins,
  11387. scales
  11388. ];
  11389. exports.Ticks = helpers_segment.Ticks;
  11390. exports.defaults = helpers_segment.defaults;
  11391. exports.Animation = Animation;
  11392. exports.Animations = Animations;
  11393. exports.ArcElement = ArcElement;
  11394. exports.BarController = BarController;
  11395. exports.BarElement = BarElement;
  11396. exports.BasePlatform = BasePlatform;
  11397. exports.BasicPlatform = BasicPlatform;
  11398. exports.BubbleController = BubbleController;
  11399. exports.CategoryScale = CategoryScale;
  11400. exports.Chart = Chart;
  11401. exports.Colors = plugin_colors;
  11402. exports.DatasetController = DatasetController;
  11403. exports.Decimation = plugin_decimation;
  11404. exports.DomPlatform = DomPlatform;
  11405. exports.DoughnutController = DoughnutController;
  11406. exports.Element = Element;
  11407. exports.Filler = index;
  11408. exports.Interaction = Interaction;
  11409. exports.Legend = plugin_legend;
  11410. exports.LineController = LineController;
  11411. exports.LineElement = LineElement;
  11412. exports.LinearScale = LinearScale;
  11413. exports.LogarithmicScale = LogarithmicScale;
  11414. exports.PieController = PieController;
  11415. exports.PointElement = PointElement;
  11416. exports.PolarAreaController = PolarAreaController;
  11417. exports.RadarController = RadarController;
  11418. exports.RadialLinearScale = RadialLinearScale;
  11419. exports.Scale = Scale;
  11420. exports.ScatterController = ScatterController;
  11421. exports.SubTitle = plugin_subtitle;
  11422. exports.TimeScale = TimeScale;
  11423. exports.TimeSeriesScale = TimeSeriesScale;
  11424. exports.Title = plugin_title;
  11425. exports.Tooltip = plugin_tooltip;
  11426. exports._adapters = adapters;
  11427. exports._detectPlatform = _detectPlatform;
  11428. exports.animator = animator;
  11429. exports.controllers = controllers;
  11430. exports.elements = elements;
  11431. exports.layouts = layouts;
  11432. exports.plugins = plugins;
  11433. exports.registerables = registerables;
  11434. exports.registry = registry;
  11435. exports.scales = scales;
  11436. //# sourceMappingURL=chart.cjs.map