chart.js 400 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304
  1. /*!
  2. * Chart.js v3.7.1
  3. * https://www.chartjs.org
  4. * (c) 2022 Chart.js Contributors
  5. * Released under the MIT License
  6. */
  7. (function (global, factory) {
  8. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  9. typeof define === 'function' && define.amd ? define(factory) :
  10. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
  11. })(this, (function () { 'use strict';
  12. function fontString(pixelSize, fontStyle, fontFamily) {
  13. return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
  14. }
  15. const requestAnimFrame = (function() {
  16. if (typeof window === 'undefined') {
  17. return function(callback) {
  18. return callback();
  19. };
  20. }
  21. return window.requestAnimationFrame;
  22. }());
  23. function throttled(fn, thisArg, updateFn) {
  24. const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args));
  25. let ticking = false;
  26. let args = [];
  27. return function(...rest) {
  28. args = updateArgs(rest);
  29. if (!ticking) {
  30. ticking = true;
  31. requestAnimFrame.call(window, () => {
  32. ticking = false;
  33. fn.apply(thisArg, args);
  34. });
  35. }
  36. };
  37. }
  38. function debounce(fn, delay) {
  39. let timeout;
  40. return function(...args) {
  41. if (delay) {
  42. clearTimeout(timeout);
  43. timeout = setTimeout(fn, delay, args);
  44. } else {
  45. fn.apply(this, args);
  46. }
  47. return delay;
  48. };
  49. }
  50. const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
  51. const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
  52. const _textX = (align, left, right, rtl) => {
  53. const check = rtl ? 'left' : 'right';
  54. return align === check ? right : align === 'center' ? (left + right) / 2 : left;
  55. };
  56. class Animator {
  57. constructor() {
  58. this._request = null;
  59. this._charts = new Map();
  60. this._running = false;
  61. this._lastDate = undefined;
  62. }
  63. _notify(chart, anims, date, type) {
  64. const callbacks = anims.listeners[type];
  65. const numSteps = anims.duration;
  66. callbacks.forEach(fn => fn({
  67. chart,
  68. initial: anims.initial,
  69. numSteps,
  70. currentStep: Math.min(date - anims.start, numSteps)
  71. }));
  72. }
  73. _refresh() {
  74. if (this._request) {
  75. return;
  76. }
  77. this._running = true;
  78. this._request = requestAnimFrame.call(window, () => {
  79. this._update();
  80. this._request = null;
  81. if (this._running) {
  82. this._refresh();
  83. }
  84. });
  85. }
  86. _update(date = Date.now()) {
  87. let remaining = 0;
  88. this._charts.forEach((anims, chart) => {
  89. if (!anims.running || !anims.items.length) {
  90. return;
  91. }
  92. const items = anims.items;
  93. let i = items.length - 1;
  94. let draw = false;
  95. let item;
  96. for (; i >= 0; --i) {
  97. item = items[i];
  98. if (item._active) {
  99. if (item._total > anims.duration) {
  100. anims.duration = item._total;
  101. }
  102. item.tick(date);
  103. draw = true;
  104. } else {
  105. items[i] = items[items.length - 1];
  106. items.pop();
  107. }
  108. }
  109. if (draw) {
  110. chart.draw();
  111. this._notify(chart, anims, date, 'progress');
  112. }
  113. if (!items.length) {
  114. anims.running = false;
  115. this._notify(chart, anims, date, 'complete');
  116. anims.initial = false;
  117. }
  118. remaining += items.length;
  119. });
  120. this._lastDate = date;
  121. if (remaining === 0) {
  122. this._running = false;
  123. }
  124. }
  125. _getAnims(chart) {
  126. const charts = this._charts;
  127. let anims = charts.get(chart);
  128. if (!anims) {
  129. anims = {
  130. running: false,
  131. initial: true,
  132. items: [],
  133. listeners: {
  134. complete: [],
  135. progress: []
  136. }
  137. };
  138. charts.set(chart, anims);
  139. }
  140. return anims;
  141. }
  142. listen(chart, event, cb) {
  143. this._getAnims(chart).listeners[event].push(cb);
  144. }
  145. add(chart, items) {
  146. if (!items || !items.length) {
  147. return;
  148. }
  149. this._getAnims(chart).items.push(...items);
  150. }
  151. has(chart) {
  152. return this._getAnims(chart).items.length > 0;
  153. }
  154. start(chart) {
  155. const anims = this._charts.get(chart);
  156. if (!anims) {
  157. return;
  158. }
  159. anims.running = true;
  160. anims.start = Date.now();
  161. anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);
  162. this._refresh();
  163. }
  164. running(chart) {
  165. if (!this._running) {
  166. return false;
  167. }
  168. const anims = this._charts.get(chart);
  169. if (!anims || !anims.running || !anims.items.length) {
  170. return false;
  171. }
  172. return true;
  173. }
  174. stop(chart) {
  175. const anims = this._charts.get(chart);
  176. if (!anims || !anims.items.length) {
  177. return;
  178. }
  179. const items = anims.items;
  180. let i = items.length - 1;
  181. for (; i >= 0; --i) {
  182. items[i].cancel();
  183. }
  184. anims.items = [];
  185. this._notify(chart, anims, Date.now(), 'complete');
  186. }
  187. remove(chart) {
  188. return this._charts.delete(chart);
  189. }
  190. }
  191. var animator = new Animator();
  192. /*!
  193. * @kurkle/color v0.1.9
  194. * https://github.com/kurkle/color#readme
  195. * (c) 2020 Jukka Kurkela
  196. * Released under the MIT License
  197. */
  198. const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15};
  199. const hex = '0123456789ABCDEF';
  200. const h1 = (b) => hex[b & 0xF];
  201. const h2 = (b) => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
  202. const eq = (b) => (((b & 0xF0) >> 4) === (b & 0xF));
  203. function isShort(v) {
  204. return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
  205. }
  206. function hexParse(str) {
  207. var len = str.length;
  208. var ret;
  209. if (str[0] === '#') {
  210. if (len === 4 || len === 5) {
  211. ret = {
  212. r: 255 & map$1[str[1]] * 17,
  213. g: 255 & map$1[str[2]] * 17,
  214. b: 255 & map$1[str[3]] * 17,
  215. a: len === 5 ? map$1[str[4]] * 17 : 255
  216. };
  217. } else if (len === 7 || len === 9) {
  218. ret = {
  219. r: map$1[str[1]] << 4 | map$1[str[2]],
  220. g: map$1[str[3]] << 4 | map$1[str[4]],
  221. b: map$1[str[5]] << 4 | map$1[str[6]],
  222. a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
  223. };
  224. }
  225. }
  226. return ret;
  227. }
  228. function hexString(v) {
  229. var f = isShort(v) ? h1 : h2;
  230. return v
  231. ? '#' + f(v.r) + f(v.g) + f(v.b) + (v.a < 255 ? f(v.a) : '')
  232. : v;
  233. }
  234. function round(v) {
  235. return v + 0.5 | 0;
  236. }
  237. const lim = (v, l, h) => Math.max(Math.min(v, h), l);
  238. function p2b(v) {
  239. return lim(round(v * 2.55), 0, 255);
  240. }
  241. function n2b(v) {
  242. return lim(round(v * 255), 0, 255);
  243. }
  244. function b2n(v) {
  245. return lim(round(v / 2.55) / 100, 0, 1);
  246. }
  247. function n2p(v) {
  248. return lim(round(v * 100), 0, 100);
  249. }
  250. const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
  251. function rgbParse(str) {
  252. const m = RGB_RE.exec(str);
  253. let a = 255;
  254. let r, g, b;
  255. if (!m) {
  256. return;
  257. }
  258. if (m[7] !== r) {
  259. const v = +m[7];
  260. a = 255 & (m[8] ? p2b(v) : v * 255);
  261. }
  262. r = +m[1];
  263. g = +m[3];
  264. b = +m[5];
  265. r = 255 & (m[2] ? p2b(r) : r);
  266. g = 255 & (m[4] ? p2b(g) : g);
  267. b = 255 & (m[6] ? p2b(b) : b);
  268. return {
  269. r: r,
  270. g: g,
  271. b: b,
  272. a: a
  273. };
  274. }
  275. function rgbString(v) {
  276. return v && (
  277. v.a < 255
  278. ? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
  279. : `rgb(${v.r}, ${v.g}, ${v.b})`
  280. );
  281. }
  282. const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
  283. function hsl2rgbn(h, s, l) {
  284. const a = s * Math.min(l, 1 - l);
  285. const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  286. return [f(0), f(8), f(4)];
  287. }
  288. function hsv2rgbn(h, s, v) {
  289. const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
  290. return [f(5), f(3), f(1)];
  291. }
  292. function hwb2rgbn(h, w, b) {
  293. const rgb = hsl2rgbn(h, 1, 0.5);
  294. let i;
  295. if (w + b > 1) {
  296. i = 1 / (w + b);
  297. w *= i;
  298. b *= i;
  299. }
  300. for (i = 0; i < 3; i++) {
  301. rgb[i] *= 1 - w - b;
  302. rgb[i] += w;
  303. }
  304. return rgb;
  305. }
  306. function rgb2hsl(v) {
  307. const range = 255;
  308. const r = v.r / range;
  309. const g = v.g / range;
  310. const b = v.b / range;
  311. const max = Math.max(r, g, b);
  312. const min = Math.min(r, g, b);
  313. const l = (max + min) / 2;
  314. let h, s, d;
  315. if (max !== min) {
  316. d = max - min;
  317. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  318. h = max === r
  319. ? ((g - b) / d) + (g < b ? 6 : 0)
  320. : max === g
  321. ? (b - r) / d + 2
  322. : (r - g) / d + 4;
  323. h = h * 60 + 0.5;
  324. }
  325. return [h | 0, s || 0, l];
  326. }
  327. function calln(f, a, b, c) {
  328. return (
  329. Array.isArray(a)
  330. ? f(a[0], a[1], a[2])
  331. : f(a, b, c)
  332. ).map(n2b);
  333. }
  334. function hsl2rgb(h, s, l) {
  335. return calln(hsl2rgbn, h, s, l);
  336. }
  337. function hwb2rgb(h, w, b) {
  338. return calln(hwb2rgbn, h, w, b);
  339. }
  340. function hsv2rgb(h, s, v) {
  341. return calln(hsv2rgbn, h, s, v);
  342. }
  343. function hue(h) {
  344. return (h % 360 + 360) % 360;
  345. }
  346. function hueParse(str) {
  347. const m = HUE_RE.exec(str);
  348. let a = 255;
  349. let v;
  350. if (!m) {
  351. return;
  352. }
  353. if (m[5] !== v) {
  354. a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
  355. }
  356. const h = hue(+m[2]);
  357. const p1 = +m[3] / 100;
  358. const p2 = +m[4] / 100;
  359. if (m[1] === 'hwb') {
  360. v = hwb2rgb(h, p1, p2);
  361. } else if (m[1] === 'hsv') {
  362. v = hsv2rgb(h, p1, p2);
  363. } else {
  364. v = hsl2rgb(h, p1, p2);
  365. }
  366. return {
  367. r: v[0],
  368. g: v[1],
  369. b: v[2],
  370. a: a
  371. };
  372. }
  373. function rotate(v, deg) {
  374. var h = rgb2hsl(v);
  375. h[0] = hue(h[0] + deg);
  376. h = hsl2rgb(h);
  377. v.r = h[0];
  378. v.g = h[1];
  379. v.b = h[2];
  380. }
  381. function hslString(v) {
  382. if (!v) {
  383. return;
  384. }
  385. const a = rgb2hsl(v);
  386. const h = a[0];
  387. const s = n2p(a[1]);
  388. const l = n2p(a[2]);
  389. return v.a < 255
  390. ? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
  391. : `hsl(${h}, ${s}%, ${l}%)`;
  392. }
  393. const map$1$1 = {
  394. x: 'dark',
  395. Z: 'light',
  396. Y: 're',
  397. X: 'blu',
  398. W: 'gr',
  399. V: 'medium',
  400. U: 'slate',
  401. A: 'ee',
  402. T: 'ol',
  403. S: 'or',
  404. B: 'ra',
  405. C: 'lateg',
  406. D: 'ights',
  407. R: 'in',
  408. Q: 'turquois',
  409. E: 'hi',
  410. P: 'ro',
  411. O: 'al',
  412. N: 'le',
  413. M: 'de',
  414. L: 'yello',
  415. F: 'en',
  416. K: 'ch',
  417. G: 'arks',
  418. H: 'ea',
  419. I: 'ightg',
  420. J: 'wh'
  421. };
  422. const names = {
  423. OiceXe: 'f0f8ff',
  424. antiquewEte: 'faebd7',
  425. aqua: 'ffff',
  426. aquamarRe: '7fffd4',
  427. azuY: 'f0ffff',
  428. beige: 'f5f5dc',
  429. bisque: 'ffe4c4',
  430. black: '0',
  431. blanKedOmond: 'ffebcd',
  432. Xe: 'ff',
  433. XeviTet: '8a2be2',
  434. bPwn: 'a52a2a',
  435. burlywood: 'deb887',
  436. caMtXe: '5f9ea0',
  437. KartYuse: '7fff00',
  438. KocTate: 'd2691e',
  439. cSO: 'ff7f50',
  440. cSnflowerXe: '6495ed',
  441. cSnsilk: 'fff8dc',
  442. crimson: 'dc143c',
  443. cyan: 'ffff',
  444. xXe: '8b',
  445. xcyan: '8b8b',
  446. xgTMnPd: 'b8860b',
  447. xWay: 'a9a9a9',
  448. xgYF: '6400',
  449. xgYy: 'a9a9a9',
  450. xkhaki: 'bdb76b',
  451. xmagFta: '8b008b',
  452. xTivegYF: '556b2f',
  453. xSange: 'ff8c00',
  454. xScEd: '9932cc',
  455. xYd: '8b0000',
  456. xsOmon: 'e9967a',
  457. xsHgYF: '8fbc8f',
  458. xUXe: '483d8b',
  459. xUWay: '2f4f4f',
  460. xUgYy: '2f4f4f',
  461. xQe: 'ced1',
  462. xviTet: '9400d3',
  463. dAppRk: 'ff1493',
  464. dApskyXe: 'bfff',
  465. dimWay: '696969',
  466. dimgYy: '696969',
  467. dodgerXe: '1e90ff',
  468. fiYbrick: 'b22222',
  469. flSOwEte: 'fffaf0',
  470. foYstWAn: '228b22',
  471. fuKsia: 'ff00ff',
  472. gaRsbSo: 'dcdcdc',
  473. ghostwEte: 'f8f8ff',
  474. gTd: 'ffd700',
  475. gTMnPd: 'daa520',
  476. Way: '808080',
  477. gYF: '8000',
  478. gYFLw: 'adff2f',
  479. gYy: '808080',
  480. honeyMw: 'f0fff0',
  481. hotpRk: 'ff69b4',
  482. RdianYd: 'cd5c5c',
  483. Rdigo: '4b0082',
  484. ivSy: 'fffff0',
  485. khaki: 'f0e68c',
  486. lavFMr: 'e6e6fa',
  487. lavFMrXsh: 'fff0f5',
  488. lawngYF: '7cfc00',
  489. NmoncEffon: 'fffacd',
  490. ZXe: 'add8e6',
  491. ZcSO: 'f08080',
  492. Zcyan: 'e0ffff',
  493. ZgTMnPdLw: 'fafad2',
  494. ZWay: 'd3d3d3',
  495. ZgYF: '90ee90',
  496. ZgYy: 'd3d3d3',
  497. ZpRk: 'ffb6c1',
  498. ZsOmon: 'ffa07a',
  499. ZsHgYF: '20b2aa',
  500. ZskyXe: '87cefa',
  501. ZUWay: '778899',
  502. ZUgYy: '778899',
  503. ZstAlXe: 'b0c4de',
  504. ZLw: 'ffffe0',
  505. lime: 'ff00',
  506. limegYF: '32cd32',
  507. lRF: 'faf0e6',
  508. magFta: 'ff00ff',
  509. maPon: '800000',
  510. VaquamarRe: '66cdaa',
  511. VXe: 'cd',
  512. VScEd: 'ba55d3',
  513. VpurpN: '9370db',
  514. VsHgYF: '3cb371',
  515. VUXe: '7b68ee',
  516. VsprRggYF: 'fa9a',
  517. VQe: '48d1cc',
  518. VviTetYd: 'c71585',
  519. midnightXe: '191970',
  520. mRtcYam: 'f5fffa',
  521. mistyPse: 'ffe4e1',
  522. moccasR: 'ffe4b5',
  523. navajowEte: 'ffdead',
  524. navy: '80',
  525. Tdlace: 'fdf5e6',
  526. Tive: '808000',
  527. TivedBb: '6b8e23',
  528. Sange: 'ffa500',
  529. SangeYd: 'ff4500',
  530. ScEd: 'da70d6',
  531. pOegTMnPd: 'eee8aa',
  532. pOegYF: '98fb98',
  533. pOeQe: 'afeeee',
  534. pOeviTetYd: 'db7093',
  535. papayawEp: 'ffefd5',
  536. pHKpuff: 'ffdab9',
  537. peru: 'cd853f',
  538. pRk: 'ffc0cb',
  539. plum: 'dda0dd',
  540. powMrXe: 'b0e0e6',
  541. purpN: '800080',
  542. YbeccapurpN: '663399',
  543. Yd: 'ff0000',
  544. Psybrown: 'bc8f8f',
  545. PyOXe: '4169e1',
  546. saddNbPwn: '8b4513',
  547. sOmon: 'fa8072',
  548. sandybPwn: 'f4a460',
  549. sHgYF: '2e8b57',
  550. sHshell: 'fff5ee',
  551. siFna: 'a0522d',
  552. silver: 'c0c0c0',
  553. skyXe: '87ceeb',
  554. UXe: '6a5acd',
  555. UWay: '708090',
  556. UgYy: '708090',
  557. snow: 'fffafa',
  558. sprRggYF: 'ff7f',
  559. stAlXe: '4682b4',
  560. tan: 'd2b48c',
  561. teO: '8080',
  562. tEstN: 'd8bfd8',
  563. tomato: 'ff6347',
  564. Qe: '40e0d0',
  565. viTet: 'ee82ee',
  566. JHt: 'f5deb3',
  567. wEte: 'ffffff',
  568. wEtesmoke: 'f5f5f5',
  569. Lw: 'ffff00',
  570. LwgYF: '9acd32'
  571. };
  572. function unpack() {
  573. const unpacked = {};
  574. const keys = Object.keys(names);
  575. const tkeys = Object.keys(map$1$1);
  576. let i, j, k, ok, nk;
  577. for (i = 0; i < keys.length; i++) {
  578. ok = nk = keys[i];
  579. for (j = 0; j < tkeys.length; j++) {
  580. k = tkeys[j];
  581. nk = nk.replace(k, map$1$1[k]);
  582. }
  583. k = parseInt(names[ok], 16);
  584. unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
  585. }
  586. return unpacked;
  587. }
  588. let names$1;
  589. function nameParse(str) {
  590. if (!names$1) {
  591. names$1 = unpack();
  592. names$1.transparent = [0, 0, 0, 0];
  593. }
  594. const a = names$1[str.toLowerCase()];
  595. return a && {
  596. r: a[0],
  597. g: a[1],
  598. b: a[2],
  599. a: a.length === 4 ? a[3] : 255
  600. };
  601. }
  602. function modHSL(v, i, ratio) {
  603. if (v) {
  604. let tmp = rgb2hsl(v);
  605. tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
  606. tmp = hsl2rgb(tmp);
  607. v.r = tmp[0];
  608. v.g = tmp[1];
  609. v.b = tmp[2];
  610. }
  611. }
  612. function clone$1(v, proto) {
  613. return v ? Object.assign(proto || {}, v) : v;
  614. }
  615. function fromObject(input) {
  616. var v = {r: 0, g: 0, b: 0, a: 255};
  617. if (Array.isArray(input)) {
  618. if (input.length >= 3) {
  619. v = {r: input[0], g: input[1], b: input[2], a: 255};
  620. if (input.length > 3) {
  621. v.a = n2b(input[3]);
  622. }
  623. }
  624. } else {
  625. v = clone$1(input, {r: 0, g: 0, b: 0, a: 1});
  626. v.a = n2b(v.a);
  627. }
  628. return v;
  629. }
  630. function functionParse(str) {
  631. if (str.charAt(0) === 'r') {
  632. return rgbParse(str);
  633. }
  634. return hueParse(str);
  635. }
  636. class Color {
  637. constructor(input) {
  638. if (input instanceof Color) {
  639. return input;
  640. }
  641. const type = typeof input;
  642. let v;
  643. if (type === 'object') {
  644. v = fromObject(input);
  645. } else if (type === 'string') {
  646. v = hexParse(input) || nameParse(input) || functionParse(input);
  647. }
  648. this._rgb = v;
  649. this._valid = !!v;
  650. }
  651. get valid() {
  652. return this._valid;
  653. }
  654. get rgb() {
  655. var v = clone$1(this._rgb);
  656. if (v) {
  657. v.a = b2n(v.a);
  658. }
  659. return v;
  660. }
  661. set rgb(obj) {
  662. this._rgb = fromObject(obj);
  663. }
  664. rgbString() {
  665. return this._valid ? rgbString(this._rgb) : this._rgb;
  666. }
  667. hexString() {
  668. return this._valid ? hexString(this._rgb) : this._rgb;
  669. }
  670. hslString() {
  671. return this._valid ? hslString(this._rgb) : this._rgb;
  672. }
  673. mix(color, weight) {
  674. const me = this;
  675. if (color) {
  676. const c1 = me.rgb;
  677. const c2 = color.rgb;
  678. let w2;
  679. const p = weight === w2 ? 0.5 : weight;
  680. const w = 2 * p - 1;
  681. const a = c1.a - c2.a;
  682. const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
  683. w2 = 1 - w1;
  684. c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
  685. c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
  686. c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
  687. c1.a = p * c1.a + (1 - p) * c2.a;
  688. me.rgb = c1;
  689. }
  690. return me;
  691. }
  692. clone() {
  693. return new Color(this.rgb);
  694. }
  695. alpha(a) {
  696. this._rgb.a = n2b(a);
  697. return this;
  698. }
  699. clearer(ratio) {
  700. const rgb = this._rgb;
  701. rgb.a *= 1 - ratio;
  702. return this;
  703. }
  704. greyscale() {
  705. const rgb = this._rgb;
  706. const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
  707. rgb.r = rgb.g = rgb.b = val;
  708. return this;
  709. }
  710. opaquer(ratio) {
  711. const rgb = this._rgb;
  712. rgb.a *= 1 + ratio;
  713. return this;
  714. }
  715. negate() {
  716. const v = this._rgb;
  717. v.r = 255 - v.r;
  718. v.g = 255 - v.g;
  719. v.b = 255 - v.b;
  720. return this;
  721. }
  722. lighten(ratio) {
  723. modHSL(this._rgb, 2, ratio);
  724. return this;
  725. }
  726. darken(ratio) {
  727. modHSL(this._rgb, 2, -ratio);
  728. return this;
  729. }
  730. saturate(ratio) {
  731. modHSL(this._rgb, 1, ratio);
  732. return this;
  733. }
  734. desaturate(ratio) {
  735. modHSL(this._rgb, 1, -ratio);
  736. return this;
  737. }
  738. rotate(deg) {
  739. rotate(this._rgb, deg);
  740. return this;
  741. }
  742. }
  743. function index_esm(input) {
  744. return new Color(input);
  745. }
  746. const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern;
  747. function color(value) {
  748. return isPatternOrGradient(value) ? value : index_esm(value);
  749. }
  750. function getHoverColor(value) {
  751. return isPatternOrGradient(value)
  752. ? value
  753. : index_esm(value).saturate(0.5).darken(0.1).hexString();
  754. }
  755. function noop() {}
  756. const uid = (function() {
  757. let id = 0;
  758. return function() {
  759. return id++;
  760. };
  761. }());
  762. function isNullOrUndef(value) {
  763. return value === null || typeof value === 'undefined';
  764. }
  765. function isArray(value) {
  766. if (Array.isArray && Array.isArray(value)) {
  767. return true;
  768. }
  769. const type = Object.prototype.toString.call(value);
  770. if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {
  771. return true;
  772. }
  773. return false;
  774. }
  775. function isObject(value) {
  776. return value !== null && Object.prototype.toString.call(value) === '[object Object]';
  777. }
  778. const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value);
  779. function finiteOrDefault(value, defaultValue) {
  780. return isNumberFinite(value) ? value : defaultValue;
  781. }
  782. function valueOrDefault(value, defaultValue) {
  783. return typeof value === 'undefined' ? defaultValue : value;
  784. }
  785. const toPercentage = (value, dimension) =>
  786. typeof value === 'string' && value.endsWith('%') ?
  787. parseFloat(value) / 100
  788. : value / dimension;
  789. const toDimension = (value, dimension) =>
  790. typeof value === 'string' && value.endsWith('%') ?
  791. parseFloat(value) / 100 * dimension
  792. : +value;
  793. function callback(fn, args, thisArg) {
  794. if (fn && typeof fn.call === 'function') {
  795. return fn.apply(thisArg, args);
  796. }
  797. }
  798. function each(loopable, fn, thisArg, reverse) {
  799. let i, len, keys;
  800. if (isArray(loopable)) {
  801. len = loopable.length;
  802. if (reverse) {
  803. for (i = len - 1; i >= 0; i--) {
  804. fn.call(thisArg, loopable[i], i);
  805. }
  806. } else {
  807. for (i = 0; i < len; i++) {
  808. fn.call(thisArg, loopable[i], i);
  809. }
  810. }
  811. } else if (isObject(loopable)) {
  812. keys = Object.keys(loopable);
  813. len = keys.length;
  814. for (i = 0; i < len; i++) {
  815. fn.call(thisArg, loopable[keys[i]], keys[i]);
  816. }
  817. }
  818. }
  819. function _elementsEqual(a0, a1) {
  820. let i, ilen, v0, v1;
  821. if (!a0 || !a1 || a0.length !== a1.length) {
  822. return false;
  823. }
  824. for (i = 0, ilen = a0.length; i < ilen; ++i) {
  825. v0 = a0[i];
  826. v1 = a1[i];
  827. if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
  828. return false;
  829. }
  830. }
  831. return true;
  832. }
  833. function clone(source) {
  834. if (isArray(source)) {
  835. return source.map(clone);
  836. }
  837. if (isObject(source)) {
  838. const target = Object.create(null);
  839. const keys = Object.keys(source);
  840. const klen = keys.length;
  841. let k = 0;
  842. for (; k < klen; ++k) {
  843. target[keys[k]] = clone(source[keys[k]]);
  844. }
  845. return target;
  846. }
  847. return source;
  848. }
  849. function isValidKey(key) {
  850. return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;
  851. }
  852. function _merger(key, target, source, options) {
  853. if (!isValidKey(key)) {
  854. return;
  855. }
  856. const tval = target[key];
  857. const sval = source[key];
  858. if (isObject(tval) && isObject(sval)) {
  859. merge(tval, sval, options);
  860. } else {
  861. target[key] = clone(sval);
  862. }
  863. }
  864. function merge(target, source, options) {
  865. const sources = isArray(source) ? source : [source];
  866. const ilen = sources.length;
  867. if (!isObject(target)) {
  868. return target;
  869. }
  870. options = options || {};
  871. const merger = options.merger || _merger;
  872. for (let i = 0; i < ilen; ++i) {
  873. source = sources[i];
  874. if (!isObject(source)) {
  875. continue;
  876. }
  877. const keys = Object.keys(source);
  878. for (let k = 0, klen = keys.length; k < klen; ++k) {
  879. merger(keys[k], target, source, options);
  880. }
  881. }
  882. return target;
  883. }
  884. function mergeIf(target, source) {
  885. return merge(target, source, {merger: _mergerIf});
  886. }
  887. function _mergerIf(key, target, source) {
  888. if (!isValidKey(key)) {
  889. return;
  890. }
  891. const tval = target[key];
  892. const sval = source[key];
  893. if (isObject(tval) && isObject(sval)) {
  894. mergeIf(tval, sval);
  895. } else if (!Object.prototype.hasOwnProperty.call(target, key)) {
  896. target[key] = clone(sval);
  897. }
  898. }
  899. function _deprecated(scope, value, previous, current) {
  900. if (value !== undefined) {
  901. console.warn(scope + ': "' + previous +
  902. '" is deprecated. Please use "' + current + '" instead');
  903. }
  904. }
  905. const emptyString = '';
  906. const dot = '.';
  907. function indexOfDotOrLength(key, start) {
  908. const idx = key.indexOf(dot, start);
  909. return idx === -1 ? key.length : idx;
  910. }
  911. function resolveObjectKey(obj, key) {
  912. if (key === emptyString) {
  913. return obj;
  914. }
  915. let pos = 0;
  916. let idx = indexOfDotOrLength(key, pos);
  917. while (obj && idx > pos) {
  918. obj = obj[key.slice(pos, idx)];
  919. pos = idx + 1;
  920. idx = indexOfDotOrLength(key, pos);
  921. }
  922. return obj;
  923. }
  924. function _capitalize(str) {
  925. return str.charAt(0).toUpperCase() + str.slice(1);
  926. }
  927. const defined = (value) => typeof value !== 'undefined';
  928. const isFunction = (value) => typeof value === 'function';
  929. const setsEqual = (a, b) => {
  930. if (a.size !== b.size) {
  931. return false;
  932. }
  933. for (const item of a) {
  934. if (!b.has(item)) {
  935. return false;
  936. }
  937. }
  938. return true;
  939. };
  940. function _isClickEvent(e) {
  941. return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
  942. }
  943. const overrides = Object.create(null);
  944. const descriptors = Object.create(null);
  945. function getScope$1(node, key) {
  946. if (!key) {
  947. return node;
  948. }
  949. const keys = key.split('.');
  950. for (let i = 0, n = keys.length; i < n; ++i) {
  951. const k = keys[i];
  952. node = node[k] || (node[k] = Object.create(null));
  953. }
  954. return node;
  955. }
  956. function set(root, scope, values) {
  957. if (typeof scope === 'string') {
  958. return merge(getScope$1(root, scope), values);
  959. }
  960. return merge(getScope$1(root, ''), scope);
  961. }
  962. class Defaults {
  963. constructor(_descriptors) {
  964. this.animation = undefined;
  965. this.backgroundColor = 'rgba(0,0,0,0.1)';
  966. this.borderColor = 'rgba(0,0,0,0.1)';
  967. this.color = '#666';
  968. this.datasets = {};
  969. this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio();
  970. this.elements = {};
  971. this.events = [
  972. 'mousemove',
  973. 'mouseout',
  974. 'click',
  975. 'touchstart',
  976. 'touchmove'
  977. ];
  978. this.font = {
  979. family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
  980. size: 12,
  981. style: 'normal',
  982. lineHeight: 1.2,
  983. weight: null
  984. };
  985. this.hover = {};
  986. this.hoverBackgroundColor = (ctx, options) => getHoverColor(options.backgroundColor);
  987. this.hoverBorderColor = (ctx, options) => getHoverColor(options.borderColor);
  988. this.hoverColor = (ctx, options) => getHoverColor(options.color);
  989. this.indexAxis = 'x';
  990. this.interaction = {
  991. mode: 'nearest',
  992. intersect: true
  993. };
  994. this.maintainAspectRatio = true;
  995. this.onHover = null;
  996. this.onClick = null;
  997. this.parsing = true;
  998. this.plugins = {};
  999. this.responsive = true;
  1000. this.scale = undefined;
  1001. this.scales = {};
  1002. this.showLine = true;
  1003. this.drawActiveElementsOnTop = true;
  1004. this.describe(_descriptors);
  1005. }
  1006. set(scope, values) {
  1007. return set(this, scope, values);
  1008. }
  1009. get(scope) {
  1010. return getScope$1(this, scope);
  1011. }
  1012. describe(scope, values) {
  1013. return set(descriptors, scope, values);
  1014. }
  1015. override(scope, values) {
  1016. return set(overrides, scope, values);
  1017. }
  1018. route(scope, name, targetScope, targetName) {
  1019. const scopeObject = getScope$1(this, scope);
  1020. const targetScopeObject = getScope$1(this, targetScope);
  1021. const privateName = '_' + name;
  1022. Object.defineProperties(scopeObject, {
  1023. [privateName]: {
  1024. value: scopeObject[name],
  1025. writable: true
  1026. },
  1027. [name]: {
  1028. enumerable: true,
  1029. get() {
  1030. const local = this[privateName];
  1031. const target = targetScopeObject[targetName];
  1032. if (isObject(local)) {
  1033. return Object.assign({}, target, local);
  1034. }
  1035. return valueOrDefault(local, target);
  1036. },
  1037. set(value) {
  1038. this[privateName] = value;
  1039. }
  1040. }
  1041. });
  1042. }
  1043. }
  1044. var defaults = new Defaults({
  1045. _scriptable: (name) => !name.startsWith('on'),
  1046. _indexable: (name) => name !== 'events',
  1047. hover: {
  1048. _fallback: 'interaction'
  1049. },
  1050. interaction: {
  1051. _scriptable: false,
  1052. _indexable: false,
  1053. }
  1054. });
  1055. function _lookup(table, value, cmp) {
  1056. cmp = cmp || ((index) => table[index] < value);
  1057. let hi = table.length - 1;
  1058. let lo = 0;
  1059. let mid;
  1060. while (hi - lo > 1) {
  1061. mid = (lo + hi) >> 1;
  1062. if (cmp(mid)) {
  1063. lo = mid;
  1064. } else {
  1065. hi = mid;
  1066. }
  1067. }
  1068. return {lo, hi};
  1069. }
  1070. const _lookupByKey = (table, key, value) =>
  1071. _lookup(table, value, index => table[index][key] < value);
  1072. const _rlookupByKey = (table, key, value) =>
  1073. _lookup(table, value, index => table[index][key] >= value);
  1074. function _filterBetween(values, min, max) {
  1075. let start = 0;
  1076. let end = values.length;
  1077. while (start < end && values[start] < min) {
  1078. start++;
  1079. }
  1080. while (end > start && values[end - 1] > max) {
  1081. end--;
  1082. }
  1083. return start > 0 || end < values.length
  1084. ? values.slice(start, end)
  1085. : values;
  1086. }
  1087. const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
  1088. function listenArrayEvents(array, listener) {
  1089. if (array._chartjs) {
  1090. array._chartjs.listeners.push(listener);
  1091. return;
  1092. }
  1093. Object.defineProperty(array, '_chartjs', {
  1094. configurable: true,
  1095. enumerable: false,
  1096. value: {
  1097. listeners: [listener]
  1098. }
  1099. });
  1100. arrayEvents.forEach((key) => {
  1101. const method = '_onData' + _capitalize(key);
  1102. const base = array[key];
  1103. Object.defineProperty(array, key, {
  1104. configurable: true,
  1105. enumerable: false,
  1106. value(...args) {
  1107. const res = base.apply(this, args);
  1108. array._chartjs.listeners.forEach((object) => {
  1109. if (typeof object[method] === 'function') {
  1110. object[method](...args);
  1111. }
  1112. });
  1113. return res;
  1114. }
  1115. });
  1116. });
  1117. }
  1118. function unlistenArrayEvents(array, listener) {
  1119. const stub = array._chartjs;
  1120. if (!stub) {
  1121. return;
  1122. }
  1123. const listeners = stub.listeners;
  1124. const index = listeners.indexOf(listener);
  1125. if (index !== -1) {
  1126. listeners.splice(index, 1);
  1127. }
  1128. if (listeners.length > 0) {
  1129. return;
  1130. }
  1131. arrayEvents.forEach((key) => {
  1132. delete array[key];
  1133. });
  1134. delete array._chartjs;
  1135. }
  1136. function _arrayUnique(items) {
  1137. const set = new Set();
  1138. let i, ilen;
  1139. for (i = 0, ilen = items.length; i < ilen; ++i) {
  1140. set.add(items[i]);
  1141. }
  1142. if (set.size === ilen) {
  1143. return items;
  1144. }
  1145. return Array.from(set);
  1146. }
  1147. const PI = Math.PI;
  1148. const TAU = 2 * PI;
  1149. const PITAU = TAU + PI;
  1150. const INFINITY = Number.POSITIVE_INFINITY;
  1151. const RAD_PER_DEG = PI / 180;
  1152. const HALF_PI = PI / 2;
  1153. const QUARTER_PI = PI / 4;
  1154. const TWO_THIRDS_PI = PI * 2 / 3;
  1155. const log10 = Math.log10;
  1156. const sign = Math.sign;
  1157. function niceNum(range) {
  1158. const roundedRange = Math.round(range);
  1159. range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
  1160. const niceRange = Math.pow(10, Math.floor(log10(range)));
  1161. const fraction = range / niceRange;
  1162. const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
  1163. return niceFraction * niceRange;
  1164. }
  1165. function _factorize(value) {
  1166. const result = [];
  1167. const sqrt = Math.sqrt(value);
  1168. let i;
  1169. for (i = 1; i < sqrt; i++) {
  1170. if (value % i === 0) {
  1171. result.push(i);
  1172. result.push(value / i);
  1173. }
  1174. }
  1175. if (sqrt === (sqrt | 0)) {
  1176. result.push(sqrt);
  1177. }
  1178. result.sort((a, b) => a - b).pop();
  1179. return result;
  1180. }
  1181. function isNumber(n) {
  1182. return !isNaN(parseFloat(n)) && isFinite(n);
  1183. }
  1184. function almostEquals(x, y, epsilon) {
  1185. return Math.abs(x - y) < epsilon;
  1186. }
  1187. function almostWhole(x, epsilon) {
  1188. const rounded = Math.round(x);
  1189. return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
  1190. }
  1191. function _setMinAndMaxByKey(array, target, property) {
  1192. let i, ilen, value;
  1193. for (i = 0, ilen = array.length; i < ilen; i++) {
  1194. value = array[i][property];
  1195. if (!isNaN(value)) {
  1196. target.min = Math.min(target.min, value);
  1197. target.max = Math.max(target.max, value);
  1198. }
  1199. }
  1200. }
  1201. function toRadians(degrees) {
  1202. return degrees * (PI / 180);
  1203. }
  1204. function toDegrees(radians) {
  1205. return radians * (180 / PI);
  1206. }
  1207. function _decimalPlaces(x) {
  1208. if (!isNumberFinite(x)) {
  1209. return;
  1210. }
  1211. let e = 1;
  1212. let p = 0;
  1213. while (Math.round(x * e) / e !== x) {
  1214. e *= 10;
  1215. p++;
  1216. }
  1217. return p;
  1218. }
  1219. function getAngleFromPoint(centrePoint, anglePoint) {
  1220. const distanceFromXCenter = anglePoint.x - centrePoint.x;
  1221. const distanceFromYCenter = anglePoint.y - centrePoint.y;
  1222. const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
  1223. let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
  1224. if (angle < (-0.5 * PI)) {
  1225. angle += TAU;
  1226. }
  1227. return {
  1228. angle,
  1229. distance: radialDistanceFromCenter
  1230. };
  1231. }
  1232. function distanceBetweenPoints(pt1, pt2) {
  1233. return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
  1234. }
  1235. function _angleDiff(a, b) {
  1236. return (a - b + PITAU) % TAU - PI;
  1237. }
  1238. function _normalizeAngle(a) {
  1239. return (a % TAU + TAU) % TAU;
  1240. }
  1241. function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
  1242. const a = _normalizeAngle(angle);
  1243. const s = _normalizeAngle(start);
  1244. const e = _normalizeAngle(end);
  1245. const angleToStart = _normalizeAngle(s - a);
  1246. const angleToEnd = _normalizeAngle(e - a);
  1247. const startToAngle = _normalizeAngle(a - s);
  1248. const endToAngle = _normalizeAngle(a - e);
  1249. return a === s || a === e || (sameAngleIsFullCircle && s === e)
  1250. || (angleToStart > angleToEnd && startToAngle < endToAngle);
  1251. }
  1252. function _limitValue(value, min, max) {
  1253. return Math.max(min, Math.min(max, value));
  1254. }
  1255. function _int16Range(value) {
  1256. return _limitValue(value, -32768, 32767);
  1257. }
  1258. function _isBetween(value, start, end, epsilon = 1e-6) {
  1259. return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
  1260. }
  1261. function _isDomSupported() {
  1262. return typeof window !== 'undefined' && typeof document !== 'undefined';
  1263. }
  1264. function _getParentNode(domNode) {
  1265. let parent = domNode.parentNode;
  1266. if (parent && parent.toString() === '[object ShadowRoot]') {
  1267. parent = parent.host;
  1268. }
  1269. return parent;
  1270. }
  1271. function parseMaxStyle(styleValue, node, parentProperty) {
  1272. let valueInPixels;
  1273. if (typeof styleValue === 'string') {
  1274. valueInPixels = parseInt(styleValue, 10);
  1275. if (styleValue.indexOf('%') !== -1) {
  1276. valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
  1277. }
  1278. } else {
  1279. valueInPixels = styleValue;
  1280. }
  1281. return valueInPixels;
  1282. }
  1283. const getComputedStyle = (element) => window.getComputedStyle(element, null);
  1284. function getStyle(el, property) {
  1285. return getComputedStyle(el).getPropertyValue(property);
  1286. }
  1287. const positions = ['top', 'right', 'bottom', 'left'];
  1288. function getPositionedStyle(styles, style, suffix) {
  1289. const result = {};
  1290. suffix = suffix ? '-' + suffix : '';
  1291. for (let i = 0; i < 4; i++) {
  1292. const pos = positions[i];
  1293. result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
  1294. }
  1295. result.width = result.left + result.right;
  1296. result.height = result.top + result.bottom;
  1297. return result;
  1298. }
  1299. const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot);
  1300. function getCanvasPosition(e, canvas) {
  1301. const touches = e.touches;
  1302. const source = touches && touches.length ? touches[0] : e;
  1303. const {offsetX, offsetY} = source;
  1304. let box = false;
  1305. let x, y;
  1306. if (useOffsetPos(offsetX, offsetY, e.target)) {
  1307. x = offsetX;
  1308. y = offsetY;
  1309. } else {
  1310. const rect = canvas.getBoundingClientRect();
  1311. x = source.clientX - rect.left;
  1312. y = source.clientY - rect.top;
  1313. box = true;
  1314. }
  1315. return {x, y, box};
  1316. }
  1317. function getRelativePosition(evt, chart) {
  1318. if ('native' in evt) {
  1319. return evt;
  1320. }
  1321. const {canvas, currentDevicePixelRatio} = chart;
  1322. const style = getComputedStyle(canvas);
  1323. const borderBox = style.boxSizing === 'border-box';
  1324. const paddings = getPositionedStyle(style, 'padding');
  1325. const borders = getPositionedStyle(style, 'border', 'width');
  1326. const {x, y, box} = getCanvasPosition(evt, canvas);
  1327. const xOffset = paddings.left + (box && borders.left);
  1328. const yOffset = paddings.top + (box && borders.top);
  1329. let {width, height} = chart;
  1330. if (borderBox) {
  1331. width -= paddings.width + borders.width;
  1332. height -= paddings.height + borders.height;
  1333. }
  1334. return {
  1335. x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
  1336. y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
  1337. };
  1338. }
  1339. function getContainerSize(canvas, width, height) {
  1340. let maxWidth, maxHeight;
  1341. if (width === undefined || height === undefined) {
  1342. const container = _getParentNode(canvas);
  1343. if (!container) {
  1344. width = canvas.clientWidth;
  1345. height = canvas.clientHeight;
  1346. } else {
  1347. const rect = container.getBoundingClientRect();
  1348. const containerStyle = getComputedStyle(container);
  1349. const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
  1350. const containerPadding = getPositionedStyle(containerStyle, 'padding');
  1351. width = rect.width - containerPadding.width - containerBorder.width;
  1352. height = rect.height - containerPadding.height - containerBorder.height;
  1353. maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
  1354. maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
  1355. }
  1356. }
  1357. return {
  1358. width,
  1359. height,
  1360. maxWidth: maxWidth || INFINITY,
  1361. maxHeight: maxHeight || INFINITY
  1362. };
  1363. }
  1364. const round1 = v => Math.round(v * 10) / 10;
  1365. function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
  1366. const style = getComputedStyle(canvas);
  1367. const margins = getPositionedStyle(style, 'margin');
  1368. const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
  1369. const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
  1370. const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
  1371. let {width, height} = containerSize;
  1372. if (style.boxSizing === 'content-box') {
  1373. const borders = getPositionedStyle(style, 'border', 'width');
  1374. const paddings = getPositionedStyle(style, 'padding');
  1375. width -= paddings.width + borders.width;
  1376. height -= paddings.height + borders.height;
  1377. }
  1378. width = Math.max(0, width - margins.width);
  1379. height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height);
  1380. width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
  1381. height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
  1382. if (width && !height) {
  1383. height = round1(width / 2);
  1384. }
  1385. return {
  1386. width,
  1387. height
  1388. };
  1389. }
  1390. function retinaScale(chart, forceRatio, forceStyle) {
  1391. const pixelRatio = forceRatio || 1;
  1392. const deviceHeight = Math.floor(chart.height * pixelRatio);
  1393. const deviceWidth = Math.floor(chart.width * pixelRatio);
  1394. chart.height = deviceHeight / pixelRatio;
  1395. chart.width = deviceWidth / pixelRatio;
  1396. const canvas = chart.canvas;
  1397. if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {
  1398. canvas.style.height = `${chart.height}px`;
  1399. canvas.style.width = `${chart.width}px`;
  1400. }
  1401. if (chart.currentDevicePixelRatio !== pixelRatio
  1402. || canvas.height !== deviceHeight
  1403. || canvas.width !== deviceWidth) {
  1404. chart.currentDevicePixelRatio = pixelRatio;
  1405. canvas.height = deviceHeight;
  1406. canvas.width = deviceWidth;
  1407. chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
  1408. return true;
  1409. }
  1410. return false;
  1411. }
  1412. const supportsEventListenerOptions = (function() {
  1413. let passiveSupported = false;
  1414. try {
  1415. const options = {
  1416. get passive() {
  1417. passiveSupported = true;
  1418. return false;
  1419. }
  1420. };
  1421. window.addEventListener('test', null, options);
  1422. window.removeEventListener('test', null, options);
  1423. } catch (e) {
  1424. }
  1425. return passiveSupported;
  1426. }());
  1427. function readUsedSize(element, property) {
  1428. const value = getStyle(element, property);
  1429. const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
  1430. return matches ? +matches[1] : undefined;
  1431. }
  1432. function binarySearch(metaset, axis, value, intersect) {
  1433. const {controller, data, _sorted} = metaset;
  1434. const iScale = controller._cachedMeta.iScale;
  1435. if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
  1436. const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
  1437. if (!intersect) {
  1438. return lookupMethod(data, axis, value);
  1439. } else if (controller._sharedOptions) {
  1440. const el = data[0];
  1441. const range = typeof el.getRange === 'function' && el.getRange(axis);
  1442. if (range) {
  1443. const start = lookupMethod(data, axis, value - range);
  1444. const end = lookupMethod(data, axis, value + range);
  1445. return {lo: start.lo, hi: end.hi};
  1446. }
  1447. }
  1448. }
  1449. return {lo: 0, hi: data.length - 1};
  1450. }
  1451. function evaluateInteractionItems(chart, axis, position, handler, intersect) {
  1452. const metasets = chart.getSortedVisibleDatasetMetas();
  1453. const value = position[axis];
  1454. for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
  1455. const {index, data} = metasets[i];
  1456. const {lo, hi} = binarySearch(metasets[i], axis, value, intersect);
  1457. for (let j = lo; j <= hi; ++j) {
  1458. const element = data[j];
  1459. if (!element.skip) {
  1460. handler(element, index, j);
  1461. }
  1462. }
  1463. }
  1464. }
  1465. function getDistanceMetricForAxis(axis) {
  1466. const useX = axis.indexOf('x') !== -1;
  1467. const useY = axis.indexOf('y') !== -1;
  1468. return function(pt1, pt2) {
  1469. const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
  1470. const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
  1471. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  1472. };
  1473. }
  1474. function getIntersectItems(chart, position, axis, useFinalPosition) {
  1475. const items = [];
  1476. if (!chart.isPointInArea(position)) {
  1477. return items;
  1478. }
  1479. const evaluationFunc = function(element, datasetIndex, index) {
  1480. if (element.inRange(position.x, position.y, useFinalPosition)) {
  1481. items.push({element, datasetIndex, index});
  1482. }
  1483. };
  1484. evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
  1485. return items;
  1486. }
  1487. function getNearestRadialItems(chart, position, axis, useFinalPosition) {
  1488. let items = [];
  1489. function evaluationFunc(element, datasetIndex, index) {
  1490. const {startAngle, endAngle} = element.getProps(['startAngle', 'endAngle'], useFinalPosition);
  1491. const {angle} = getAngleFromPoint(element, {x: position.x, y: position.y});
  1492. if (_angleBetween(angle, startAngle, endAngle)) {
  1493. items.push({element, datasetIndex, index});
  1494. }
  1495. }
  1496. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  1497. return items;
  1498. }
  1499. function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) {
  1500. let items = [];
  1501. const distanceMetric = getDistanceMetricForAxis(axis);
  1502. let minDistance = Number.POSITIVE_INFINITY;
  1503. function evaluationFunc(element, datasetIndex, index) {
  1504. const inRange = element.inRange(position.x, position.y, useFinalPosition);
  1505. if (intersect && !inRange) {
  1506. return;
  1507. }
  1508. const center = element.getCenterPoint(useFinalPosition);
  1509. const pointInArea = chart.isPointInArea(center);
  1510. if (!pointInArea && !inRange) {
  1511. return;
  1512. }
  1513. const distance = distanceMetric(position, center);
  1514. if (distance < minDistance) {
  1515. items = [{element, datasetIndex, index}];
  1516. minDistance = distance;
  1517. } else if (distance === minDistance) {
  1518. items.push({element, datasetIndex, index});
  1519. }
  1520. }
  1521. evaluateInteractionItems(chart, axis, position, evaluationFunc);
  1522. return items;
  1523. }
  1524. function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
  1525. if (!chart.isPointInArea(position)) {
  1526. return [];
  1527. }
  1528. return axis === 'r' && !intersect
  1529. ? getNearestRadialItems(chart, position, axis, useFinalPosition)
  1530. : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition);
  1531. }
  1532. function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
  1533. const items = [];
  1534. const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
  1535. let intersectsItem = false;
  1536. evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index) => {
  1537. if (element[rangeMethod](position[axis], useFinalPosition)) {
  1538. items.push({element, datasetIndex, index});
  1539. intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
  1540. }
  1541. });
  1542. if (intersect && !intersectsItem) {
  1543. return [];
  1544. }
  1545. return items;
  1546. }
  1547. var Interaction = {
  1548. evaluateInteractionItems,
  1549. modes: {
  1550. index(chart, e, options, useFinalPosition) {
  1551. const position = getRelativePosition(e, chart);
  1552. const axis = options.axis || 'x';
  1553. const items = options.intersect
  1554. ? getIntersectItems(chart, position, axis, useFinalPosition)
  1555. : getNearestItems(chart, position, axis, false, useFinalPosition);
  1556. const elements = [];
  1557. if (!items.length) {
  1558. return [];
  1559. }
  1560. chart.getSortedVisibleDatasetMetas().forEach((meta) => {
  1561. const index = items[0].index;
  1562. const element = meta.data[index];
  1563. if (element && !element.skip) {
  1564. elements.push({element, datasetIndex: meta.index, index});
  1565. }
  1566. });
  1567. return elements;
  1568. },
  1569. dataset(chart, e, options, useFinalPosition) {
  1570. const position = getRelativePosition(e, chart);
  1571. const axis = options.axis || 'xy';
  1572. let items = options.intersect
  1573. ? getIntersectItems(chart, position, axis, useFinalPosition) :
  1574. getNearestItems(chart, position, axis, false, useFinalPosition);
  1575. if (items.length > 0) {
  1576. const datasetIndex = items[0].datasetIndex;
  1577. const data = chart.getDatasetMeta(datasetIndex).data;
  1578. items = [];
  1579. for (let i = 0; i < data.length; ++i) {
  1580. items.push({element: data[i], datasetIndex, index: i});
  1581. }
  1582. }
  1583. return items;
  1584. },
  1585. point(chart, e, options, useFinalPosition) {
  1586. const position = getRelativePosition(e, chart);
  1587. const axis = options.axis || 'xy';
  1588. return getIntersectItems(chart, position, axis, useFinalPosition);
  1589. },
  1590. nearest(chart, e, options, useFinalPosition) {
  1591. const position = getRelativePosition(e, chart);
  1592. const axis = options.axis || 'xy';
  1593. return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
  1594. },
  1595. x(chart, e, options, useFinalPosition) {
  1596. const position = getRelativePosition(e, chart);
  1597. return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
  1598. },
  1599. y(chart, e, options, useFinalPosition) {
  1600. const position = getRelativePosition(e, chart);
  1601. return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
  1602. }
  1603. }
  1604. };
  1605. function toFontString(font) {
  1606. if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
  1607. return null;
  1608. }
  1609. return (font.style ? font.style + ' ' : '')
  1610. + (font.weight ? font.weight + ' ' : '')
  1611. + font.size + 'px '
  1612. + font.family;
  1613. }
  1614. function _measureText(ctx, data, gc, longest, string) {
  1615. let textWidth = data[string];
  1616. if (!textWidth) {
  1617. textWidth = data[string] = ctx.measureText(string).width;
  1618. gc.push(string);
  1619. }
  1620. if (textWidth > longest) {
  1621. longest = textWidth;
  1622. }
  1623. return longest;
  1624. }
  1625. function _longestText(ctx, font, arrayOfThings, cache) {
  1626. cache = cache || {};
  1627. let data = cache.data = cache.data || {};
  1628. let gc = cache.garbageCollect = cache.garbageCollect || [];
  1629. if (cache.font !== font) {
  1630. data = cache.data = {};
  1631. gc = cache.garbageCollect = [];
  1632. cache.font = font;
  1633. }
  1634. ctx.save();
  1635. ctx.font = font;
  1636. let longest = 0;
  1637. const ilen = arrayOfThings.length;
  1638. let i, j, jlen, thing, nestedThing;
  1639. for (i = 0; i < ilen; i++) {
  1640. thing = arrayOfThings[i];
  1641. if (thing !== undefined && thing !== null && isArray(thing) !== true) {
  1642. longest = _measureText(ctx, data, gc, longest, thing);
  1643. } else if (isArray(thing)) {
  1644. for (j = 0, jlen = thing.length; j < jlen; j++) {
  1645. nestedThing = thing[j];
  1646. if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
  1647. longest = _measureText(ctx, data, gc, longest, nestedThing);
  1648. }
  1649. }
  1650. }
  1651. }
  1652. ctx.restore();
  1653. const gcLen = gc.length / 2;
  1654. if (gcLen > arrayOfThings.length) {
  1655. for (i = 0; i < gcLen; i++) {
  1656. delete data[gc[i]];
  1657. }
  1658. gc.splice(0, gcLen);
  1659. }
  1660. return longest;
  1661. }
  1662. function _alignPixel(chart, pixel, width) {
  1663. const devicePixelRatio = chart.currentDevicePixelRatio;
  1664. const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
  1665. return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
  1666. }
  1667. function clearCanvas(canvas, ctx) {
  1668. ctx = ctx || canvas.getContext('2d');
  1669. ctx.save();
  1670. ctx.resetTransform();
  1671. ctx.clearRect(0, 0, canvas.width, canvas.height);
  1672. ctx.restore();
  1673. }
  1674. function drawPoint(ctx, options, x, y) {
  1675. let type, xOffset, yOffset, size, cornerRadius;
  1676. const style = options.pointStyle;
  1677. const rotation = options.rotation;
  1678. const radius = options.radius;
  1679. let rad = (rotation || 0) * RAD_PER_DEG;
  1680. if (style && typeof style === 'object') {
  1681. type = style.toString();
  1682. if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
  1683. ctx.save();
  1684. ctx.translate(x, y);
  1685. ctx.rotate(rad);
  1686. ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
  1687. ctx.restore();
  1688. return;
  1689. }
  1690. }
  1691. if (isNaN(radius) || radius <= 0) {
  1692. return;
  1693. }
  1694. ctx.beginPath();
  1695. switch (style) {
  1696. default:
  1697. ctx.arc(x, y, radius, 0, TAU);
  1698. ctx.closePath();
  1699. break;
  1700. case 'triangle':
  1701. ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1702. rad += TWO_THIRDS_PI;
  1703. ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1704. rad += TWO_THIRDS_PI;
  1705. ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
  1706. ctx.closePath();
  1707. break;
  1708. case 'rectRounded':
  1709. cornerRadius = radius * 0.516;
  1710. size = radius - cornerRadius;
  1711. xOffset = Math.cos(rad + QUARTER_PI) * size;
  1712. yOffset = Math.sin(rad + QUARTER_PI) * size;
  1713. ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
  1714. ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
  1715. ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
  1716. ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
  1717. ctx.closePath();
  1718. break;
  1719. case 'rect':
  1720. if (!rotation) {
  1721. size = Math.SQRT1_2 * radius;
  1722. ctx.rect(x - size, y - size, 2 * size, 2 * size);
  1723. break;
  1724. }
  1725. rad += QUARTER_PI;
  1726. case 'rectRot':
  1727. xOffset = Math.cos(rad) * radius;
  1728. yOffset = Math.sin(rad) * radius;
  1729. ctx.moveTo(x - xOffset, y - yOffset);
  1730. ctx.lineTo(x + yOffset, y - xOffset);
  1731. ctx.lineTo(x + xOffset, y + yOffset);
  1732. ctx.lineTo(x - yOffset, y + xOffset);
  1733. ctx.closePath();
  1734. break;
  1735. case 'crossRot':
  1736. rad += QUARTER_PI;
  1737. case 'cross':
  1738. xOffset = Math.cos(rad) * radius;
  1739. yOffset = Math.sin(rad) * radius;
  1740. ctx.moveTo(x - xOffset, y - yOffset);
  1741. ctx.lineTo(x + xOffset, y + yOffset);
  1742. ctx.moveTo(x + yOffset, y - xOffset);
  1743. ctx.lineTo(x - yOffset, y + xOffset);
  1744. break;
  1745. case 'star':
  1746. xOffset = Math.cos(rad) * radius;
  1747. yOffset = Math.sin(rad) * radius;
  1748. ctx.moveTo(x - xOffset, y - yOffset);
  1749. ctx.lineTo(x + xOffset, y + yOffset);
  1750. ctx.moveTo(x + yOffset, y - xOffset);
  1751. ctx.lineTo(x - yOffset, y + xOffset);
  1752. rad += QUARTER_PI;
  1753. xOffset = Math.cos(rad) * radius;
  1754. yOffset = Math.sin(rad) * radius;
  1755. ctx.moveTo(x - xOffset, y - yOffset);
  1756. ctx.lineTo(x + xOffset, y + yOffset);
  1757. ctx.moveTo(x + yOffset, y - xOffset);
  1758. ctx.lineTo(x - yOffset, y + xOffset);
  1759. break;
  1760. case 'line':
  1761. xOffset = Math.cos(rad) * radius;
  1762. yOffset = Math.sin(rad) * radius;
  1763. ctx.moveTo(x - xOffset, y - yOffset);
  1764. ctx.lineTo(x + xOffset, y + yOffset);
  1765. break;
  1766. case 'dash':
  1767. ctx.moveTo(x, y);
  1768. ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
  1769. break;
  1770. }
  1771. ctx.fill();
  1772. if (options.borderWidth > 0) {
  1773. ctx.stroke();
  1774. }
  1775. }
  1776. function _isPointInArea(point, area, margin) {
  1777. margin = margin || 0.5;
  1778. return !area || (point && point.x > area.left - margin && point.x < area.right + margin &&
  1779. point.y > area.top - margin && point.y < area.bottom + margin);
  1780. }
  1781. function clipArea(ctx, area) {
  1782. ctx.save();
  1783. ctx.beginPath();
  1784. ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
  1785. ctx.clip();
  1786. }
  1787. function unclipArea(ctx) {
  1788. ctx.restore();
  1789. }
  1790. function _steppedLineTo(ctx, previous, target, flip, mode) {
  1791. if (!previous) {
  1792. return ctx.lineTo(target.x, target.y);
  1793. }
  1794. if (mode === 'middle') {
  1795. const midpoint = (previous.x + target.x) / 2.0;
  1796. ctx.lineTo(midpoint, previous.y);
  1797. ctx.lineTo(midpoint, target.y);
  1798. } else if (mode === 'after' !== !!flip) {
  1799. ctx.lineTo(previous.x, target.y);
  1800. } else {
  1801. ctx.lineTo(target.x, previous.y);
  1802. }
  1803. ctx.lineTo(target.x, target.y);
  1804. }
  1805. function _bezierCurveTo(ctx, previous, target, flip) {
  1806. if (!previous) {
  1807. return ctx.lineTo(target.x, target.y);
  1808. }
  1809. ctx.bezierCurveTo(
  1810. flip ? previous.cp1x : previous.cp2x,
  1811. flip ? previous.cp1y : previous.cp2y,
  1812. flip ? target.cp2x : target.cp1x,
  1813. flip ? target.cp2y : target.cp1y,
  1814. target.x,
  1815. target.y);
  1816. }
  1817. function renderText(ctx, text, x, y, font, opts = {}) {
  1818. const lines = isArray(text) ? text : [text];
  1819. const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
  1820. let i, line;
  1821. ctx.save();
  1822. ctx.font = font.string;
  1823. setRenderOpts(ctx, opts);
  1824. for (i = 0; i < lines.length; ++i) {
  1825. line = lines[i];
  1826. if (stroke) {
  1827. if (opts.strokeColor) {
  1828. ctx.strokeStyle = opts.strokeColor;
  1829. }
  1830. if (!isNullOrUndef(opts.strokeWidth)) {
  1831. ctx.lineWidth = opts.strokeWidth;
  1832. }
  1833. ctx.strokeText(line, x, y, opts.maxWidth);
  1834. }
  1835. ctx.fillText(line, x, y, opts.maxWidth);
  1836. decorateText(ctx, x, y, line, opts);
  1837. y += font.lineHeight;
  1838. }
  1839. ctx.restore();
  1840. }
  1841. function setRenderOpts(ctx, opts) {
  1842. if (opts.translation) {
  1843. ctx.translate(opts.translation[0], opts.translation[1]);
  1844. }
  1845. if (!isNullOrUndef(opts.rotation)) {
  1846. ctx.rotate(opts.rotation);
  1847. }
  1848. if (opts.color) {
  1849. ctx.fillStyle = opts.color;
  1850. }
  1851. if (opts.textAlign) {
  1852. ctx.textAlign = opts.textAlign;
  1853. }
  1854. if (opts.textBaseline) {
  1855. ctx.textBaseline = opts.textBaseline;
  1856. }
  1857. }
  1858. function decorateText(ctx, x, y, line, opts) {
  1859. if (opts.strikethrough || opts.underline) {
  1860. const metrics = ctx.measureText(line);
  1861. const left = x - metrics.actualBoundingBoxLeft;
  1862. const right = x + metrics.actualBoundingBoxRight;
  1863. const top = y - metrics.actualBoundingBoxAscent;
  1864. const bottom = y + metrics.actualBoundingBoxDescent;
  1865. const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
  1866. ctx.strokeStyle = ctx.fillStyle;
  1867. ctx.beginPath();
  1868. ctx.lineWidth = opts.decorationWidth || 2;
  1869. ctx.moveTo(left, yDecoration);
  1870. ctx.lineTo(right, yDecoration);
  1871. ctx.stroke();
  1872. }
  1873. }
  1874. function addRoundedRectPath(ctx, rect) {
  1875. const {x, y, w, h, radius} = rect;
  1876. ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);
  1877. ctx.lineTo(x, y + h - radius.bottomLeft);
  1878. ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
  1879. ctx.lineTo(x + w - radius.bottomRight, y + h);
  1880. ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
  1881. ctx.lineTo(x + w, y + radius.topRight);
  1882. ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
  1883. ctx.lineTo(x + radius.topLeft, y);
  1884. }
  1885. const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
  1886. const FONT_STYLE = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);
  1887. function toLineHeight(value, size) {
  1888. const matches = ('' + value).match(LINE_HEIGHT);
  1889. if (!matches || matches[1] === 'normal') {
  1890. return size * 1.2;
  1891. }
  1892. value = +matches[2];
  1893. switch (matches[3]) {
  1894. case 'px':
  1895. return value;
  1896. case '%':
  1897. value /= 100;
  1898. break;
  1899. }
  1900. return size * value;
  1901. }
  1902. const numberOrZero = v => +v || 0;
  1903. function _readValueToProps(value, props) {
  1904. const ret = {};
  1905. const objProps = isObject(props);
  1906. const keys = objProps ? Object.keys(props) : props;
  1907. const read = isObject(value)
  1908. ? objProps
  1909. ? prop => valueOrDefault(value[prop], value[props[prop]])
  1910. : prop => value[prop]
  1911. : () => value;
  1912. for (const prop of keys) {
  1913. ret[prop] = numberOrZero(read(prop));
  1914. }
  1915. return ret;
  1916. }
  1917. function toTRBL(value) {
  1918. return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'});
  1919. }
  1920. function toTRBLCorners(value) {
  1921. return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);
  1922. }
  1923. function toPadding(value) {
  1924. const obj = toTRBL(value);
  1925. obj.width = obj.left + obj.right;
  1926. obj.height = obj.top + obj.bottom;
  1927. return obj;
  1928. }
  1929. function toFont(options, fallback) {
  1930. options = options || {};
  1931. fallback = fallback || defaults.font;
  1932. let size = valueOrDefault(options.size, fallback.size);
  1933. if (typeof size === 'string') {
  1934. size = parseInt(size, 10);
  1935. }
  1936. let style = valueOrDefault(options.style, fallback.style);
  1937. if (style && !('' + style).match(FONT_STYLE)) {
  1938. console.warn('Invalid font style specified: "' + style + '"');
  1939. style = '';
  1940. }
  1941. const font = {
  1942. family: valueOrDefault(options.family, fallback.family),
  1943. lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
  1944. size,
  1945. style,
  1946. weight: valueOrDefault(options.weight, fallback.weight),
  1947. string: ''
  1948. };
  1949. font.string = toFontString(font);
  1950. return font;
  1951. }
  1952. function resolve(inputs, context, index, info) {
  1953. let cacheable = true;
  1954. let i, ilen, value;
  1955. for (i = 0, ilen = inputs.length; i < ilen; ++i) {
  1956. value = inputs[i];
  1957. if (value === undefined) {
  1958. continue;
  1959. }
  1960. if (context !== undefined && typeof value === 'function') {
  1961. value = value(context);
  1962. cacheable = false;
  1963. }
  1964. if (index !== undefined && isArray(value)) {
  1965. value = value[index % value.length];
  1966. cacheable = false;
  1967. }
  1968. if (value !== undefined) {
  1969. if (info && !cacheable) {
  1970. info.cacheable = false;
  1971. }
  1972. return value;
  1973. }
  1974. }
  1975. }
  1976. function _addGrace(minmax, grace, beginAtZero) {
  1977. const {min, max} = minmax;
  1978. const change = toDimension(grace, (max - min) / 2);
  1979. const keepZero = (value, add) => beginAtZero && value === 0 ? 0 : value + add;
  1980. return {
  1981. min: keepZero(min, -Math.abs(change)),
  1982. max: keepZero(max, change)
  1983. };
  1984. }
  1985. function createContext(parentContext, context) {
  1986. return Object.assign(Object.create(parentContext), context);
  1987. }
  1988. const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
  1989. function filterByPosition(array, position) {
  1990. return array.filter(v => v.pos === position);
  1991. }
  1992. function filterDynamicPositionByAxis(array, axis) {
  1993. return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
  1994. }
  1995. function sortByWeight(array, reverse) {
  1996. return array.sort((a, b) => {
  1997. const v0 = reverse ? b : a;
  1998. const v1 = reverse ? a : b;
  1999. return v0.weight === v1.weight ?
  2000. v0.index - v1.index :
  2001. v0.weight - v1.weight;
  2002. });
  2003. }
  2004. function wrapBoxes(boxes) {
  2005. const layoutBoxes = [];
  2006. let i, ilen, box, pos, stack, stackWeight;
  2007. for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
  2008. box = boxes[i];
  2009. ({position: pos, options: {stack, stackWeight = 1}} = box);
  2010. layoutBoxes.push({
  2011. index: i,
  2012. box,
  2013. pos,
  2014. horizontal: box.isHorizontal(),
  2015. weight: box.weight,
  2016. stack: stack && (pos + stack),
  2017. stackWeight
  2018. });
  2019. }
  2020. return layoutBoxes;
  2021. }
  2022. function buildStacks(layouts) {
  2023. const stacks = {};
  2024. for (const wrap of layouts) {
  2025. const {stack, pos, stackWeight} = wrap;
  2026. if (!stack || !STATIC_POSITIONS.includes(pos)) {
  2027. continue;
  2028. }
  2029. const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0});
  2030. _stack.count++;
  2031. _stack.weight += stackWeight;
  2032. }
  2033. return stacks;
  2034. }
  2035. function setLayoutDims(layouts, params) {
  2036. const stacks = buildStacks(layouts);
  2037. const {vBoxMaxWidth, hBoxMaxHeight} = params;
  2038. let i, ilen, layout;
  2039. for (i = 0, ilen = layouts.length; i < ilen; ++i) {
  2040. layout = layouts[i];
  2041. const {fullSize} = layout.box;
  2042. const stack = stacks[layout.stack];
  2043. const factor = stack && layout.stackWeight / stack.weight;
  2044. if (layout.horizontal) {
  2045. layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
  2046. layout.height = hBoxMaxHeight;
  2047. } else {
  2048. layout.width = vBoxMaxWidth;
  2049. layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
  2050. }
  2051. }
  2052. return stacks;
  2053. }
  2054. function buildLayoutBoxes(boxes) {
  2055. const layoutBoxes = wrapBoxes(boxes);
  2056. const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true);
  2057. const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
  2058. const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
  2059. const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
  2060. const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
  2061. const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
  2062. const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
  2063. return {
  2064. fullSize,
  2065. leftAndTop: left.concat(top),
  2066. rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
  2067. chartArea: filterByPosition(layoutBoxes, 'chartArea'),
  2068. vertical: left.concat(right).concat(centerVertical),
  2069. horizontal: top.concat(bottom).concat(centerHorizontal)
  2070. };
  2071. }
  2072. function getCombinedMax(maxPadding, chartArea, a, b) {
  2073. return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  2074. }
  2075. function updateMaxPadding(maxPadding, boxPadding) {
  2076. maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
  2077. maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
  2078. maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
  2079. maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  2080. }
  2081. function updateDims(chartArea, params, layout, stacks) {
  2082. const {pos, box} = layout;
  2083. const maxPadding = chartArea.maxPadding;
  2084. if (!isObject(pos)) {
  2085. if (layout.size) {
  2086. chartArea[pos] -= layout.size;
  2087. }
  2088. const stack = stacks[layout.stack] || {size: 0, count: 1};
  2089. stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
  2090. layout.size = stack.size / stack.count;
  2091. chartArea[pos] += layout.size;
  2092. }
  2093. if (box.getPadding) {
  2094. updateMaxPadding(maxPadding, box.getPadding());
  2095. }
  2096. const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
  2097. const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
  2098. const widthChanged = newWidth !== chartArea.w;
  2099. const heightChanged = newHeight !== chartArea.h;
  2100. chartArea.w = newWidth;
  2101. chartArea.h = newHeight;
  2102. return layout.horizontal
  2103. ? {same: widthChanged, other: heightChanged}
  2104. : {same: heightChanged, other: widthChanged};
  2105. }
  2106. function handleMaxPadding(chartArea) {
  2107. const maxPadding = chartArea.maxPadding;
  2108. function updatePos(pos) {
  2109. const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
  2110. chartArea[pos] += change;
  2111. return change;
  2112. }
  2113. chartArea.y += updatePos('top');
  2114. chartArea.x += updatePos('left');
  2115. updatePos('right');
  2116. updatePos('bottom');
  2117. }
  2118. function getMargins(horizontal, chartArea) {
  2119. const maxPadding = chartArea.maxPadding;
  2120. function marginForPositions(positions) {
  2121. const margin = {left: 0, top: 0, right: 0, bottom: 0};
  2122. positions.forEach((pos) => {
  2123. margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
  2124. });
  2125. return margin;
  2126. }
  2127. return horizontal
  2128. ? marginForPositions(['left', 'right'])
  2129. : marginForPositions(['top', 'bottom']);
  2130. }
  2131. function fitBoxes(boxes, chartArea, params, stacks) {
  2132. const refitBoxes = [];
  2133. let i, ilen, layout, box, refit, changed;
  2134. for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {
  2135. layout = boxes[i];
  2136. box = layout.box;
  2137. box.update(
  2138. layout.width || chartArea.w,
  2139. layout.height || chartArea.h,
  2140. getMargins(layout.horizontal, chartArea)
  2141. );
  2142. const {same, other} = updateDims(chartArea, params, layout, stacks);
  2143. refit |= same && refitBoxes.length;
  2144. changed = changed || other;
  2145. if (!box.fullSize) {
  2146. refitBoxes.push(layout);
  2147. }
  2148. }
  2149. return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  2150. }
  2151. function setBoxDims(box, left, top, width, height) {
  2152. box.top = top;
  2153. box.left = left;
  2154. box.right = left + width;
  2155. box.bottom = top + height;
  2156. box.width = width;
  2157. box.height = height;
  2158. }
  2159. function placeBoxes(boxes, chartArea, params, stacks) {
  2160. const userPadding = params.padding;
  2161. let {x, y} = chartArea;
  2162. for (const layout of boxes) {
  2163. const box = layout.box;
  2164. const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1};
  2165. const weight = (layout.stackWeight / stack.weight) || 1;
  2166. if (layout.horizontal) {
  2167. const width = chartArea.w * weight;
  2168. const height = stack.size || box.height;
  2169. if (defined(stack.start)) {
  2170. y = stack.start;
  2171. }
  2172. if (box.fullSize) {
  2173. setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
  2174. } else {
  2175. setBoxDims(box, chartArea.left + stack.placed, y, width, height);
  2176. }
  2177. stack.start = y;
  2178. stack.placed += width;
  2179. y = box.bottom;
  2180. } else {
  2181. const height = chartArea.h * weight;
  2182. const width = stack.size || box.width;
  2183. if (defined(stack.start)) {
  2184. x = stack.start;
  2185. }
  2186. if (box.fullSize) {
  2187. setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
  2188. } else {
  2189. setBoxDims(box, x, chartArea.top + stack.placed, width, height);
  2190. }
  2191. stack.start = x;
  2192. stack.placed += height;
  2193. x = box.right;
  2194. }
  2195. }
  2196. chartArea.x = x;
  2197. chartArea.y = y;
  2198. }
  2199. defaults.set('layout', {
  2200. autoPadding: true,
  2201. padding: {
  2202. top: 0,
  2203. right: 0,
  2204. bottom: 0,
  2205. left: 0
  2206. }
  2207. });
  2208. var layouts = {
  2209. addBox(chart, item) {
  2210. if (!chart.boxes) {
  2211. chart.boxes = [];
  2212. }
  2213. item.fullSize = item.fullSize || false;
  2214. item.position = item.position || 'top';
  2215. item.weight = item.weight || 0;
  2216. item._layers = item._layers || function() {
  2217. return [{
  2218. z: 0,
  2219. draw(chartArea) {
  2220. item.draw(chartArea);
  2221. }
  2222. }];
  2223. };
  2224. chart.boxes.push(item);
  2225. },
  2226. removeBox(chart, layoutItem) {
  2227. const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
  2228. if (index !== -1) {
  2229. chart.boxes.splice(index, 1);
  2230. }
  2231. },
  2232. configure(chart, item, options) {
  2233. item.fullSize = options.fullSize;
  2234. item.position = options.position;
  2235. item.weight = options.weight;
  2236. },
  2237. update(chart, width, height, minPadding) {
  2238. if (!chart) {
  2239. return;
  2240. }
  2241. const padding = toPadding(chart.options.layout.padding);
  2242. const availableWidth = Math.max(width - padding.width, 0);
  2243. const availableHeight = Math.max(height - padding.height, 0);
  2244. const boxes = buildLayoutBoxes(chart.boxes);
  2245. const verticalBoxes = boxes.vertical;
  2246. const horizontalBoxes = boxes.horizontal;
  2247. each(chart.boxes, box => {
  2248. if (typeof box.beforeLayout === 'function') {
  2249. box.beforeLayout();
  2250. }
  2251. });
  2252. const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) =>
  2253. wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
  2254. const params = Object.freeze({
  2255. outerWidth: width,
  2256. outerHeight: height,
  2257. padding,
  2258. availableWidth,
  2259. availableHeight,
  2260. vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
  2261. hBoxMaxHeight: availableHeight / 2
  2262. });
  2263. const maxPadding = Object.assign({}, padding);
  2264. updateMaxPadding(maxPadding, toPadding(minPadding));
  2265. const chartArea = Object.assign({
  2266. maxPadding,
  2267. w: availableWidth,
  2268. h: availableHeight,
  2269. x: padding.left,
  2270. y: padding.top
  2271. }, padding);
  2272. const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
  2273. fitBoxes(boxes.fullSize, chartArea, params, stacks);
  2274. fitBoxes(verticalBoxes, chartArea, params, stacks);
  2275. if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
  2276. fitBoxes(verticalBoxes, chartArea, params, stacks);
  2277. }
  2278. handleMaxPadding(chartArea);
  2279. placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
  2280. chartArea.x += chartArea.w;
  2281. chartArea.y += chartArea.h;
  2282. placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
  2283. chart.chartArea = {
  2284. left: chartArea.left,
  2285. top: chartArea.top,
  2286. right: chartArea.left + chartArea.w,
  2287. bottom: chartArea.top + chartArea.h,
  2288. height: chartArea.h,
  2289. width: chartArea.w,
  2290. };
  2291. each(boxes.chartArea, (layout) => {
  2292. const box = layout.box;
  2293. Object.assign(box, chart.chartArea);
  2294. box.update(chartArea.w, chartArea.h, {left: 0, top: 0, right: 0, bottom: 0});
  2295. });
  2296. }
  2297. };
  2298. function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) {
  2299. if (!defined(fallback)) {
  2300. fallback = _resolve('_fallback', scopes);
  2301. }
  2302. const cache = {
  2303. [Symbol.toStringTag]: 'Object',
  2304. _cacheable: true,
  2305. _scopes: scopes,
  2306. _rootScopes: rootScopes,
  2307. _fallback: fallback,
  2308. _getTarget: getTarget,
  2309. override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback),
  2310. };
  2311. return new Proxy(cache, {
  2312. deleteProperty(target, prop) {
  2313. delete target[prop];
  2314. delete target._keys;
  2315. delete scopes[0][prop];
  2316. return true;
  2317. },
  2318. get(target, prop) {
  2319. return _cached(target, prop,
  2320. () => _resolveWithPrefixes(prop, prefixes, scopes, target));
  2321. },
  2322. getOwnPropertyDescriptor(target, prop) {
  2323. return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
  2324. },
  2325. getPrototypeOf() {
  2326. return Reflect.getPrototypeOf(scopes[0]);
  2327. },
  2328. has(target, prop) {
  2329. return getKeysFromAllScopes(target).includes(prop);
  2330. },
  2331. ownKeys(target) {
  2332. return getKeysFromAllScopes(target);
  2333. },
  2334. set(target, prop, value) {
  2335. const storage = target._storage || (target._storage = getTarget());
  2336. target[prop] = storage[prop] = value;
  2337. delete target._keys;
  2338. return true;
  2339. }
  2340. });
  2341. }
  2342. function _attachContext(proxy, context, subProxy, descriptorDefaults) {
  2343. const cache = {
  2344. _cacheable: false,
  2345. _proxy: proxy,
  2346. _context: context,
  2347. _subProxy: subProxy,
  2348. _stack: new Set(),
  2349. _descriptors: _descriptors(proxy, descriptorDefaults),
  2350. setContext: (ctx) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),
  2351. override: (scope) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
  2352. };
  2353. return new Proxy(cache, {
  2354. deleteProperty(target, prop) {
  2355. delete target[prop];
  2356. delete proxy[prop];
  2357. return true;
  2358. },
  2359. get(target, prop, receiver) {
  2360. return _cached(target, prop,
  2361. () => _resolveWithContext(target, prop, receiver));
  2362. },
  2363. getOwnPropertyDescriptor(target, prop) {
  2364. return target._descriptors.allKeys
  2365. ? Reflect.has(proxy, prop) ? {enumerable: true, configurable: true} : undefined
  2366. : Reflect.getOwnPropertyDescriptor(proxy, prop);
  2367. },
  2368. getPrototypeOf() {
  2369. return Reflect.getPrototypeOf(proxy);
  2370. },
  2371. has(target, prop) {
  2372. return Reflect.has(proxy, prop);
  2373. },
  2374. ownKeys() {
  2375. return Reflect.ownKeys(proxy);
  2376. },
  2377. set(target, prop, value) {
  2378. proxy[prop] = value;
  2379. delete target[prop];
  2380. return true;
  2381. }
  2382. });
  2383. }
  2384. function _descriptors(proxy, defaults = {scriptable: true, indexable: true}) {
  2385. const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;
  2386. return {
  2387. allKeys: _allKeys,
  2388. scriptable: _scriptable,
  2389. indexable: _indexable,
  2390. isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,
  2391. isIndexable: isFunction(_indexable) ? _indexable : () => _indexable
  2392. };
  2393. }
  2394. const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name;
  2395. const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' &&
  2396. (Object.getPrototypeOf(value) === null || value.constructor === Object);
  2397. function _cached(target, prop, resolve) {
  2398. if (Object.prototype.hasOwnProperty.call(target, prop)) {
  2399. return target[prop];
  2400. }
  2401. const value = resolve();
  2402. target[prop] = value;
  2403. return value;
  2404. }
  2405. function _resolveWithContext(target, prop, receiver) {
  2406. const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  2407. let value = _proxy[prop];
  2408. if (isFunction(value) && descriptors.isScriptable(prop)) {
  2409. value = _resolveScriptable(prop, value, target, receiver);
  2410. }
  2411. if (isArray(value) && value.length) {
  2412. value = _resolveArray(prop, value, target, descriptors.isIndexable);
  2413. }
  2414. if (needsSubResolver(prop, value)) {
  2415. value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
  2416. }
  2417. return value;
  2418. }
  2419. function _resolveScriptable(prop, value, target, receiver) {
  2420. const {_proxy, _context, _subProxy, _stack} = target;
  2421. if (_stack.has(prop)) {
  2422. throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
  2423. }
  2424. _stack.add(prop);
  2425. value = value(_context, _subProxy || receiver);
  2426. _stack.delete(prop);
  2427. if (needsSubResolver(prop, value)) {
  2428. value = createSubResolver(_proxy._scopes, _proxy, prop, value);
  2429. }
  2430. return value;
  2431. }
  2432. function _resolveArray(prop, value, target, isIndexable) {
  2433. const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;
  2434. if (defined(_context.index) && isIndexable(prop)) {
  2435. value = value[_context.index % value.length];
  2436. } else if (isObject(value[0])) {
  2437. const arr = value;
  2438. const scopes = _proxy._scopes.filter(s => s !== arr);
  2439. value = [];
  2440. for (const item of arr) {
  2441. const resolver = createSubResolver(scopes, _proxy, prop, item);
  2442. value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
  2443. }
  2444. }
  2445. return value;
  2446. }
  2447. function resolveFallback(fallback, prop, value) {
  2448. return isFunction(fallback) ? fallback(prop, value) : fallback;
  2449. }
  2450. const getScope = (key, parent) => key === true ? parent
  2451. : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
  2452. function addScopes(set, parentScopes, key, parentFallback, value) {
  2453. for (const parent of parentScopes) {
  2454. const scope = getScope(key, parent);
  2455. if (scope) {
  2456. set.add(scope);
  2457. const fallback = resolveFallback(scope._fallback, key, value);
  2458. if (defined(fallback) && fallback !== key && fallback !== parentFallback) {
  2459. return fallback;
  2460. }
  2461. } else if (scope === false && defined(parentFallback) && key !== parentFallback) {
  2462. return null;
  2463. }
  2464. }
  2465. return false;
  2466. }
  2467. function createSubResolver(parentScopes, resolver, prop, value) {
  2468. const rootScopes = resolver._rootScopes;
  2469. const fallback = resolveFallback(resolver._fallback, prop, value);
  2470. const allScopes = [...parentScopes, ...rootScopes];
  2471. const set = new Set();
  2472. set.add(value);
  2473. let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
  2474. if (key === null) {
  2475. return false;
  2476. }
  2477. if (defined(fallback) && fallback !== prop) {
  2478. key = addScopesFromKey(set, allScopes, fallback, key, value);
  2479. if (key === null) {
  2480. return false;
  2481. }
  2482. }
  2483. return _createResolver(Array.from(set), [''], rootScopes, fallback,
  2484. () => subGetTarget(resolver, prop, value));
  2485. }
  2486. function addScopesFromKey(set, allScopes, key, fallback, item) {
  2487. while (key) {
  2488. key = addScopes(set, allScopes, key, fallback, item);
  2489. }
  2490. return key;
  2491. }
  2492. function subGetTarget(resolver, prop, value) {
  2493. const parent = resolver._getTarget();
  2494. if (!(prop in parent)) {
  2495. parent[prop] = {};
  2496. }
  2497. const target = parent[prop];
  2498. if (isArray(target) && isObject(value)) {
  2499. return value;
  2500. }
  2501. return target;
  2502. }
  2503. function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
  2504. let value;
  2505. for (const prefix of prefixes) {
  2506. value = _resolve(readKey(prefix, prop), scopes);
  2507. if (defined(value)) {
  2508. return needsSubResolver(prop, value)
  2509. ? createSubResolver(scopes, proxy, prop, value)
  2510. : value;
  2511. }
  2512. }
  2513. }
  2514. function _resolve(key, scopes) {
  2515. for (const scope of scopes) {
  2516. if (!scope) {
  2517. continue;
  2518. }
  2519. const value = scope[key];
  2520. if (defined(value)) {
  2521. return value;
  2522. }
  2523. }
  2524. }
  2525. function getKeysFromAllScopes(target) {
  2526. let keys = target._keys;
  2527. if (!keys) {
  2528. keys = target._keys = resolveKeysFromAllScopes(target._scopes);
  2529. }
  2530. return keys;
  2531. }
  2532. function resolveKeysFromAllScopes(scopes) {
  2533. const set = new Set();
  2534. for (const scope of scopes) {
  2535. for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) {
  2536. set.add(key);
  2537. }
  2538. }
  2539. return Array.from(set);
  2540. }
  2541. function _parseObjectDataRadialScale(meta, data, start, count) {
  2542. const {iScale} = meta;
  2543. const {key = 'r'} = this._parsing;
  2544. const parsed = new Array(count);
  2545. let i, ilen, index, item;
  2546. for (i = 0, ilen = count; i < ilen; ++i) {
  2547. index = i + start;
  2548. item = data[index];
  2549. parsed[i] = {
  2550. r: iScale.parse(resolveObjectKey(item, key), index)
  2551. };
  2552. }
  2553. return parsed;
  2554. }
  2555. const EPSILON = Number.EPSILON || 1e-14;
  2556. const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
  2557. const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';
  2558. function splineCurve(firstPoint, middlePoint, afterPoint, t) {
  2559. const previous = firstPoint.skip ? middlePoint : firstPoint;
  2560. const current = middlePoint;
  2561. const next = afterPoint.skip ? middlePoint : afterPoint;
  2562. const d01 = distanceBetweenPoints(current, previous);
  2563. const d12 = distanceBetweenPoints(next, current);
  2564. let s01 = d01 / (d01 + d12);
  2565. let s12 = d12 / (d01 + d12);
  2566. s01 = isNaN(s01) ? 0 : s01;
  2567. s12 = isNaN(s12) ? 0 : s12;
  2568. const fa = t * s01;
  2569. const fb = t * s12;
  2570. return {
  2571. previous: {
  2572. x: current.x - fa * (next.x - previous.x),
  2573. y: current.y - fa * (next.y - previous.y)
  2574. },
  2575. next: {
  2576. x: current.x + fb * (next.x - previous.x),
  2577. y: current.y + fb * (next.y - previous.y)
  2578. }
  2579. };
  2580. }
  2581. function monotoneAdjust(points, deltaK, mK) {
  2582. const pointsLen = points.length;
  2583. let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
  2584. let pointAfter = getPoint(points, 0);
  2585. for (let i = 0; i < pointsLen - 1; ++i) {
  2586. pointCurrent = pointAfter;
  2587. pointAfter = getPoint(points, i + 1);
  2588. if (!pointCurrent || !pointAfter) {
  2589. continue;
  2590. }
  2591. if (almostEquals(deltaK[i], 0, EPSILON)) {
  2592. mK[i] = mK[i + 1] = 0;
  2593. continue;
  2594. }
  2595. alphaK = mK[i] / deltaK[i];
  2596. betaK = mK[i + 1] / deltaK[i];
  2597. squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
  2598. if (squaredMagnitude <= 9) {
  2599. continue;
  2600. }
  2601. tauK = 3 / Math.sqrt(squaredMagnitude);
  2602. mK[i] = alphaK * tauK * deltaK[i];
  2603. mK[i + 1] = betaK * tauK * deltaK[i];
  2604. }
  2605. }
  2606. function monotoneCompute(points, mK, indexAxis = 'x') {
  2607. const valueAxis = getValueAxis(indexAxis);
  2608. const pointsLen = points.length;
  2609. let delta, pointBefore, pointCurrent;
  2610. let pointAfter = getPoint(points, 0);
  2611. for (let i = 0; i < pointsLen; ++i) {
  2612. pointBefore = pointCurrent;
  2613. pointCurrent = pointAfter;
  2614. pointAfter = getPoint(points, i + 1);
  2615. if (!pointCurrent) {
  2616. continue;
  2617. }
  2618. const iPixel = pointCurrent[indexAxis];
  2619. const vPixel = pointCurrent[valueAxis];
  2620. if (pointBefore) {
  2621. delta = (iPixel - pointBefore[indexAxis]) / 3;
  2622. pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
  2623. pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
  2624. }
  2625. if (pointAfter) {
  2626. delta = (pointAfter[indexAxis] - iPixel) / 3;
  2627. pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
  2628. pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
  2629. }
  2630. }
  2631. }
  2632. function splineCurveMonotone(points, indexAxis = 'x') {
  2633. const valueAxis = getValueAxis(indexAxis);
  2634. const pointsLen = points.length;
  2635. const deltaK = Array(pointsLen).fill(0);
  2636. const mK = Array(pointsLen);
  2637. let i, pointBefore, pointCurrent;
  2638. let pointAfter = getPoint(points, 0);
  2639. for (i = 0; i < pointsLen; ++i) {
  2640. pointBefore = pointCurrent;
  2641. pointCurrent = pointAfter;
  2642. pointAfter = getPoint(points, i + 1);
  2643. if (!pointCurrent) {
  2644. continue;
  2645. }
  2646. if (pointAfter) {
  2647. const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
  2648. deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
  2649. }
  2650. mK[i] = !pointBefore ? deltaK[i]
  2651. : !pointAfter ? deltaK[i - 1]
  2652. : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0
  2653. : (deltaK[i - 1] + deltaK[i]) / 2;
  2654. }
  2655. monotoneAdjust(points, deltaK, mK);
  2656. monotoneCompute(points, mK, indexAxis);
  2657. }
  2658. function capControlPoint(pt, min, max) {
  2659. return Math.max(Math.min(pt, max), min);
  2660. }
  2661. function capBezierPoints(points, area) {
  2662. let i, ilen, point, inArea, inAreaPrev;
  2663. let inAreaNext = _isPointInArea(points[0], area);
  2664. for (i = 0, ilen = points.length; i < ilen; ++i) {
  2665. inAreaPrev = inArea;
  2666. inArea = inAreaNext;
  2667. inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
  2668. if (!inArea) {
  2669. continue;
  2670. }
  2671. point = points[i];
  2672. if (inAreaPrev) {
  2673. point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
  2674. point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
  2675. }
  2676. if (inAreaNext) {
  2677. point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
  2678. point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
  2679. }
  2680. }
  2681. }
  2682. function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
  2683. let i, ilen, point, controlPoints;
  2684. if (options.spanGaps) {
  2685. points = points.filter((pt) => !pt.skip);
  2686. }
  2687. if (options.cubicInterpolationMode === 'monotone') {
  2688. splineCurveMonotone(points, indexAxis);
  2689. } else {
  2690. let prev = loop ? points[points.length - 1] : points[0];
  2691. for (i = 0, ilen = points.length; i < ilen; ++i) {
  2692. point = points[i];
  2693. controlPoints = splineCurve(
  2694. prev,
  2695. point,
  2696. points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen],
  2697. options.tension
  2698. );
  2699. point.cp1x = controlPoints.previous.x;
  2700. point.cp1y = controlPoints.previous.y;
  2701. point.cp2x = controlPoints.next.x;
  2702. point.cp2y = controlPoints.next.y;
  2703. prev = point;
  2704. }
  2705. }
  2706. if (options.capBezierPoints) {
  2707. capBezierPoints(points, area);
  2708. }
  2709. }
  2710. const atEdge = (t) => t === 0 || t === 1;
  2711. const elasticIn = (t, s, p) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
  2712. const elasticOut = (t, s, p) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
  2713. const effects = {
  2714. linear: t => t,
  2715. easeInQuad: t => t * t,
  2716. easeOutQuad: t => -t * (t - 2),
  2717. easeInOutQuad: t => ((t /= 0.5) < 1)
  2718. ? 0.5 * t * t
  2719. : -0.5 * ((--t) * (t - 2) - 1),
  2720. easeInCubic: t => t * t * t,
  2721. easeOutCubic: t => (t -= 1) * t * t + 1,
  2722. easeInOutCubic: t => ((t /= 0.5) < 1)
  2723. ? 0.5 * t * t * t
  2724. : 0.5 * ((t -= 2) * t * t + 2),
  2725. easeInQuart: t => t * t * t * t,
  2726. easeOutQuart: t => -((t -= 1) * t * t * t - 1),
  2727. easeInOutQuart: t => ((t /= 0.5) < 1)
  2728. ? 0.5 * t * t * t * t
  2729. : -0.5 * ((t -= 2) * t * t * t - 2),
  2730. easeInQuint: t => t * t * t * t * t,
  2731. easeOutQuint: t => (t -= 1) * t * t * t * t + 1,
  2732. easeInOutQuint: t => ((t /= 0.5) < 1)
  2733. ? 0.5 * t * t * t * t * t
  2734. : 0.5 * ((t -= 2) * t * t * t * t + 2),
  2735. easeInSine: t => -Math.cos(t * HALF_PI) + 1,
  2736. easeOutSine: t => Math.sin(t * HALF_PI),
  2737. easeInOutSine: t => -0.5 * (Math.cos(PI * t) - 1),
  2738. easeInExpo: t => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)),
  2739. easeOutExpo: t => (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1,
  2740. easeInOutExpo: t => atEdge(t) ? t : t < 0.5
  2741. ? 0.5 * Math.pow(2, 10 * (t * 2 - 1))
  2742. : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
  2743. easeInCirc: t => (t >= 1) ? t : -(Math.sqrt(1 - t * t) - 1),
  2744. easeOutCirc: t => Math.sqrt(1 - (t -= 1) * t),
  2745. easeInOutCirc: t => ((t /= 0.5) < 1)
  2746. ? -0.5 * (Math.sqrt(1 - t * t) - 1)
  2747. : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
  2748. easeInElastic: t => atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
  2749. easeOutElastic: t => atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
  2750. easeInOutElastic(t) {
  2751. const s = 0.1125;
  2752. const p = 0.45;
  2753. return atEdge(t) ? t :
  2754. t < 0.5
  2755. ? 0.5 * elasticIn(t * 2, s, p)
  2756. : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
  2757. },
  2758. easeInBack(t) {
  2759. const s = 1.70158;
  2760. return t * t * ((s + 1) * t - s);
  2761. },
  2762. easeOutBack(t) {
  2763. const s = 1.70158;
  2764. return (t -= 1) * t * ((s + 1) * t + s) + 1;
  2765. },
  2766. easeInOutBack(t) {
  2767. let s = 1.70158;
  2768. if ((t /= 0.5) < 1) {
  2769. return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
  2770. }
  2771. return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
  2772. },
  2773. easeInBounce: t => 1 - effects.easeOutBounce(1 - t),
  2774. easeOutBounce(t) {
  2775. const m = 7.5625;
  2776. const d = 2.75;
  2777. if (t < (1 / d)) {
  2778. return m * t * t;
  2779. }
  2780. if (t < (2 / d)) {
  2781. return m * (t -= (1.5 / d)) * t + 0.75;
  2782. }
  2783. if (t < (2.5 / d)) {
  2784. return m * (t -= (2.25 / d)) * t + 0.9375;
  2785. }
  2786. return m * (t -= (2.625 / d)) * t + 0.984375;
  2787. },
  2788. easeInOutBounce: t => (t < 0.5)
  2789. ? effects.easeInBounce(t * 2) * 0.5
  2790. : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5,
  2791. };
  2792. function _pointInLine(p1, p2, t, mode) {
  2793. return {
  2794. x: p1.x + t * (p2.x - p1.x),
  2795. y: p1.y + t * (p2.y - p1.y)
  2796. };
  2797. }
  2798. function _steppedInterpolation(p1, p2, t, mode) {
  2799. return {
  2800. x: p1.x + t * (p2.x - p1.x),
  2801. y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y
  2802. : mode === 'after' ? t < 1 ? p1.y : p2.y
  2803. : t > 0 ? p2.y : p1.y
  2804. };
  2805. }
  2806. function _bezierInterpolation(p1, p2, t, mode) {
  2807. const cp1 = {x: p1.cp2x, y: p1.cp2y};
  2808. const cp2 = {x: p2.cp1x, y: p2.cp1y};
  2809. const a = _pointInLine(p1, cp1, t);
  2810. const b = _pointInLine(cp1, cp2, t);
  2811. const c = _pointInLine(cp2, p2, t);
  2812. const d = _pointInLine(a, b, t);
  2813. const e = _pointInLine(b, c, t);
  2814. return _pointInLine(d, e, t);
  2815. }
  2816. const intlCache = new Map();
  2817. function getNumberFormat(locale, options) {
  2818. options = options || {};
  2819. const cacheKey = locale + JSON.stringify(options);
  2820. let formatter = intlCache.get(cacheKey);
  2821. if (!formatter) {
  2822. formatter = new Intl.NumberFormat(locale, options);
  2823. intlCache.set(cacheKey, formatter);
  2824. }
  2825. return formatter;
  2826. }
  2827. function formatNumber(num, locale, options) {
  2828. return getNumberFormat(locale, options).format(num);
  2829. }
  2830. const getRightToLeftAdapter = function(rectX, width) {
  2831. return {
  2832. x(x) {
  2833. return rectX + rectX + width - x;
  2834. },
  2835. setWidth(w) {
  2836. width = w;
  2837. },
  2838. textAlign(align) {
  2839. if (align === 'center') {
  2840. return align;
  2841. }
  2842. return align === 'right' ? 'left' : 'right';
  2843. },
  2844. xPlus(x, value) {
  2845. return x - value;
  2846. },
  2847. leftForLtr(x, itemWidth) {
  2848. return x - itemWidth;
  2849. },
  2850. };
  2851. };
  2852. const getLeftToRightAdapter = function() {
  2853. return {
  2854. x(x) {
  2855. return x;
  2856. },
  2857. setWidth(w) {
  2858. },
  2859. textAlign(align) {
  2860. return align;
  2861. },
  2862. xPlus(x, value) {
  2863. return x + value;
  2864. },
  2865. leftForLtr(x, _itemWidth) {
  2866. return x;
  2867. },
  2868. };
  2869. };
  2870. function getRtlAdapter(rtl, rectX, width) {
  2871. return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
  2872. }
  2873. function overrideTextDirection(ctx, direction) {
  2874. let style, original;
  2875. if (direction === 'ltr' || direction === 'rtl') {
  2876. style = ctx.canvas.style;
  2877. original = [
  2878. style.getPropertyValue('direction'),
  2879. style.getPropertyPriority('direction'),
  2880. ];
  2881. style.setProperty('direction', direction, 'important');
  2882. ctx.prevTextDirection = original;
  2883. }
  2884. }
  2885. function restoreTextDirection(ctx, original) {
  2886. if (original !== undefined) {
  2887. delete ctx.prevTextDirection;
  2888. ctx.canvas.style.setProperty('direction', original[0], original[1]);
  2889. }
  2890. }
  2891. function propertyFn(property) {
  2892. if (property === 'angle') {
  2893. return {
  2894. between: _angleBetween,
  2895. compare: _angleDiff,
  2896. normalize: _normalizeAngle,
  2897. };
  2898. }
  2899. return {
  2900. between: _isBetween,
  2901. compare: (a, b) => a - b,
  2902. normalize: x => x
  2903. };
  2904. }
  2905. function normalizeSegment({start, end, count, loop, style}) {
  2906. return {
  2907. start: start % count,
  2908. end: end % count,
  2909. loop: loop && (end - start + 1) % count === 0,
  2910. style
  2911. };
  2912. }
  2913. function getSegment(segment, points, bounds) {
  2914. const {property, start: startBound, end: endBound} = bounds;
  2915. const {between, normalize} = propertyFn(property);
  2916. const count = points.length;
  2917. let {start, end, loop} = segment;
  2918. let i, ilen;
  2919. if (loop) {
  2920. start += count;
  2921. end += count;
  2922. for (i = 0, ilen = count; i < ilen; ++i) {
  2923. if (!between(normalize(points[start % count][property]), startBound, endBound)) {
  2924. break;
  2925. }
  2926. start--;
  2927. end--;
  2928. }
  2929. start %= count;
  2930. end %= count;
  2931. }
  2932. if (end < start) {
  2933. end += count;
  2934. }
  2935. return {start, end, loop, style: segment.style};
  2936. }
  2937. function _boundSegment(segment, points, bounds) {
  2938. if (!bounds) {
  2939. return [segment];
  2940. }
  2941. const {property, start: startBound, end: endBound} = bounds;
  2942. const count = points.length;
  2943. const {compare, between, normalize} = propertyFn(property);
  2944. const {start, end, loop, style} = getSegment(segment, points, bounds);
  2945. const result = [];
  2946. let inside = false;
  2947. let subStart = null;
  2948. let value, point, prevValue;
  2949. const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
  2950. const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
  2951. const shouldStart = () => inside || startIsBefore();
  2952. const shouldStop = () => !inside || endIsBefore();
  2953. for (let i = start, prev = start; i <= end; ++i) {
  2954. point = points[i % count];
  2955. if (point.skip) {
  2956. continue;
  2957. }
  2958. value = normalize(point[property]);
  2959. if (value === prevValue) {
  2960. continue;
  2961. }
  2962. inside = between(value, startBound, endBound);
  2963. if (subStart === null && shouldStart()) {
  2964. subStart = compare(value, startBound) === 0 ? i : prev;
  2965. }
  2966. if (subStart !== null && shouldStop()) {
  2967. result.push(normalizeSegment({start: subStart, end: i, loop, count, style}));
  2968. subStart = null;
  2969. }
  2970. prev = i;
  2971. prevValue = value;
  2972. }
  2973. if (subStart !== null) {
  2974. result.push(normalizeSegment({start: subStart, end, loop, count, style}));
  2975. }
  2976. return result;
  2977. }
  2978. function _boundSegments(line, bounds) {
  2979. const result = [];
  2980. const segments = line.segments;
  2981. for (let i = 0; i < segments.length; i++) {
  2982. const sub = _boundSegment(segments[i], line.points, bounds);
  2983. if (sub.length) {
  2984. result.push(...sub);
  2985. }
  2986. }
  2987. return result;
  2988. }
  2989. function findStartAndEnd(points, count, loop, spanGaps) {
  2990. let start = 0;
  2991. let end = count - 1;
  2992. if (loop && !spanGaps) {
  2993. while (start < count && !points[start].skip) {
  2994. start++;
  2995. }
  2996. }
  2997. while (start < count && points[start].skip) {
  2998. start++;
  2999. }
  3000. start %= count;
  3001. if (loop) {
  3002. end += start;
  3003. }
  3004. while (end > start && points[end % count].skip) {
  3005. end--;
  3006. }
  3007. end %= count;
  3008. return {start, end};
  3009. }
  3010. function solidSegments(points, start, max, loop) {
  3011. const count = points.length;
  3012. const result = [];
  3013. let last = start;
  3014. let prev = points[start];
  3015. let end;
  3016. for (end = start + 1; end <= max; ++end) {
  3017. const cur = points[end % count];
  3018. if (cur.skip || cur.stop) {
  3019. if (!prev.skip) {
  3020. loop = false;
  3021. result.push({start: start % count, end: (end - 1) % count, loop});
  3022. start = last = cur.stop ? end : null;
  3023. }
  3024. } else {
  3025. last = end;
  3026. if (prev.skip) {
  3027. start = end;
  3028. }
  3029. }
  3030. prev = cur;
  3031. }
  3032. if (last !== null) {
  3033. result.push({start: start % count, end: last % count, loop});
  3034. }
  3035. return result;
  3036. }
  3037. function _computeSegments(line, segmentOptions) {
  3038. const points = line.points;
  3039. const spanGaps = line.options.spanGaps;
  3040. const count = points.length;
  3041. if (!count) {
  3042. return [];
  3043. }
  3044. const loop = !!line._loop;
  3045. const {start, end} = findStartAndEnd(points, count, loop, spanGaps);
  3046. if (spanGaps === true) {
  3047. return splitByStyles(line, [{start, end, loop}], points, segmentOptions);
  3048. }
  3049. const max = end < start ? end + count : end;
  3050. const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
  3051. return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
  3052. }
  3053. function splitByStyles(line, segments, points, segmentOptions) {
  3054. if (!segmentOptions || !segmentOptions.setContext || !points) {
  3055. return segments;
  3056. }
  3057. return doSplitByStyles(line, segments, points, segmentOptions);
  3058. }
  3059. function doSplitByStyles(line, segments, points, segmentOptions) {
  3060. const chartContext = line._chart.getContext();
  3061. const baseStyle = readStyle(line.options);
  3062. const {_datasetIndex: datasetIndex, options: {spanGaps}} = line;
  3063. const count = points.length;
  3064. const result = [];
  3065. let prevStyle = baseStyle;
  3066. let start = segments[0].start;
  3067. let i = start;
  3068. function addStyle(s, e, l, st) {
  3069. const dir = spanGaps ? -1 : 1;
  3070. if (s === e) {
  3071. return;
  3072. }
  3073. s += count;
  3074. while (points[s % count].skip) {
  3075. s -= dir;
  3076. }
  3077. while (points[e % count].skip) {
  3078. e += dir;
  3079. }
  3080. if (s % count !== e % count) {
  3081. result.push({start: s % count, end: e % count, loop: l, style: st});
  3082. prevStyle = st;
  3083. start = e % count;
  3084. }
  3085. }
  3086. for (const segment of segments) {
  3087. start = spanGaps ? start : segment.start;
  3088. let prev = points[start % count];
  3089. let style;
  3090. for (i = start + 1; i <= segment.end; i++) {
  3091. const pt = points[i % count];
  3092. style = readStyle(segmentOptions.setContext(createContext(chartContext, {
  3093. type: 'segment',
  3094. p0: prev,
  3095. p1: pt,
  3096. p0DataIndex: (i - 1) % count,
  3097. p1DataIndex: i % count,
  3098. datasetIndex
  3099. })));
  3100. if (styleChanged(style, prevStyle)) {
  3101. addStyle(start, i - 1, segment.loop, prevStyle);
  3102. }
  3103. prev = pt;
  3104. prevStyle = style;
  3105. }
  3106. if (start < i - 1) {
  3107. addStyle(start, i - 1, segment.loop, prevStyle);
  3108. }
  3109. }
  3110. return result;
  3111. }
  3112. function readStyle(options) {
  3113. return {
  3114. backgroundColor: options.backgroundColor,
  3115. borderCapStyle: options.borderCapStyle,
  3116. borderDash: options.borderDash,
  3117. borderDashOffset: options.borderDashOffset,
  3118. borderJoinStyle: options.borderJoinStyle,
  3119. borderWidth: options.borderWidth,
  3120. borderColor: options.borderColor
  3121. };
  3122. }
  3123. function styleChanged(style, prevStyle) {
  3124. return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle);
  3125. }
  3126. var helpers = /*#__PURE__*/Object.freeze({
  3127. __proto__: null,
  3128. easingEffects: effects,
  3129. color: color,
  3130. getHoverColor: getHoverColor,
  3131. noop: noop,
  3132. uid: uid,
  3133. isNullOrUndef: isNullOrUndef,
  3134. isArray: isArray,
  3135. isObject: isObject,
  3136. isFinite: isNumberFinite,
  3137. finiteOrDefault: finiteOrDefault,
  3138. valueOrDefault: valueOrDefault,
  3139. toPercentage: toPercentage,
  3140. toDimension: toDimension,
  3141. callback: callback,
  3142. each: each,
  3143. _elementsEqual: _elementsEqual,
  3144. clone: clone,
  3145. _merger: _merger,
  3146. merge: merge,
  3147. mergeIf: mergeIf,
  3148. _mergerIf: _mergerIf,
  3149. _deprecated: _deprecated,
  3150. resolveObjectKey: resolveObjectKey,
  3151. _capitalize: _capitalize,
  3152. defined: defined,
  3153. isFunction: isFunction,
  3154. setsEqual: setsEqual,
  3155. _isClickEvent: _isClickEvent,
  3156. toFontString: toFontString,
  3157. _measureText: _measureText,
  3158. _longestText: _longestText,
  3159. _alignPixel: _alignPixel,
  3160. clearCanvas: clearCanvas,
  3161. drawPoint: drawPoint,
  3162. _isPointInArea: _isPointInArea,
  3163. clipArea: clipArea,
  3164. unclipArea: unclipArea,
  3165. _steppedLineTo: _steppedLineTo,
  3166. _bezierCurveTo: _bezierCurveTo,
  3167. renderText: renderText,
  3168. addRoundedRectPath: addRoundedRectPath,
  3169. _lookup: _lookup,
  3170. _lookupByKey: _lookupByKey,
  3171. _rlookupByKey: _rlookupByKey,
  3172. _filterBetween: _filterBetween,
  3173. listenArrayEvents: listenArrayEvents,
  3174. unlistenArrayEvents: unlistenArrayEvents,
  3175. _arrayUnique: _arrayUnique,
  3176. _createResolver: _createResolver,
  3177. _attachContext: _attachContext,
  3178. _descriptors: _descriptors,
  3179. _parseObjectDataRadialScale: _parseObjectDataRadialScale,
  3180. splineCurve: splineCurve,
  3181. splineCurveMonotone: splineCurveMonotone,
  3182. _updateBezierControlPoints: _updateBezierControlPoints,
  3183. _isDomSupported: _isDomSupported,
  3184. _getParentNode: _getParentNode,
  3185. getStyle: getStyle,
  3186. getRelativePosition: getRelativePosition,
  3187. getMaximumSize: getMaximumSize,
  3188. retinaScale: retinaScale,
  3189. supportsEventListenerOptions: supportsEventListenerOptions,
  3190. readUsedSize: readUsedSize,
  3191. fontString: fontString,
  3192. requestAnimFrame: requestAnimFrame,
  3193. throttled: throttled,
  3194. debounce: debounce,
  3195. _toLeftRightCenter: _toLeftRightCenter,
  3196. _alignStartEnd: _alignStartEnd,
  3197. _textX: _textX,
  3198. _pointInLine: _pointInLine,
  3199. _steppedInterpolation: _steppedInterpolation,
  3200. _bezierInterpolation: _bezierInterpolation,
  3201. formatNumber: formatNumber,
  3202. toLineHeight: toLineHeight,
  3203. _readValueToProps: _readValueToProps,
  3204. toTRBL: toTRBL,
  3205. toTRBLCorners: toTRBLCorners,
  3206. toPadding: toPadding,
  3207. toFont: toFont,
  3208. resolve: resolve,
  3209. _addGrace: _addGrace,
  3210. createContext: createContext,
  3211. PI: PI,
  3212. TAU: TAU,
  3213. PITAU: PITAU,
  3214. INFINITY: INFINITY,
  3215. RAD_PER_DEG: RAD_PER_DEG,
  3216. HALF_PI: HALF_PI,
  3217. QUARTER_PI: QUARTER_PI,
  3218. TWO_THIRDS_PI: TWO_THIRDS_PI,
  3219. log10: log10,
  3220. sign: sign,
  3221. niceNum: niceNum,
  3222. _factorize: _factorize,
  3223. isNumber: isNumber,
  3224. almostEquals: almostEquals,
  3225. almostWhole: almostWhole,
  3226. _setMinAndMaxByKey: _setMinAndMaxByKey,
  3227. toRadians: toRadians,
  3228. toDegrees: toDegrees,
  3229. _decimalPlaces: _decimalPlaces,
  3230. getAngleFromPoint: getAngleFromPoint,
  3231. distanceBetweenPoints: distanceBetweenPoints,
  3232. _angleDiff: _angleDiff,
  3233. _normalizeAngle: _normalizeAngle,
  3234. _angleBetween: _angleBetween,
  3235. _limitValue: _limitValue,
  3236. _int16Range: _int16Range,
  3237. _isBetween: _isBetween,
  3238. getRtlAdapter: getRtlAdapter,
  3239. overrideTextDirection: overrideTextDirection,
  3240. restoreTextDirection: restoreTextDirection,
  3241. _boundSegment: _boundSegment,
  3242. _boundSegments: _boundSegments,
  3243. _computeSegments: _computeSegments
  3244. });
  3245. class BasePlatform {
  3246. acquireContext(canvas, aspectRatio) {}
  3247. releaseContext(context) {
  3248. return false;
  3249. }
  3250. addEventListener(chart, type, listener) {}
  3251. removeEventListener(chart, type, listener) {}
  3252. getDevicePixelRatio() {
  3253. return 1;
  3254. }
  3255. getMaximumSize(element, width, height, aspectRatio) {
  3256. width = Math.max(0, width || element.width);
  3257. height = height || element.height;
  3258. return {
  3259. width,
  3260. height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
  3261. };
  3262. }
  3263. isAttached(canvas) {
  3264. return true;
  3265. }
  3266. updateConfig(config) {
  3267. }
  3268. }
  3269. class BasicPlatform extends BasePlatform {
  3270. acquireContext(item) {
  3271. return item && item.getContext && item.getContext('2d') || null;
  3272. }
  3273. updateConfig(config) {
  3274. config.options.animation = false;
  3275. }
  3276. }
  3277. const EXPANDO_KEY = '$chartjs';
  3278. const EVENT_TYPES = {
  3279. touchstart: 'mousedown',
  3280. touchmove: 'mousemove',
  3281. touchend: 'mouseup',
  3282. pointerenter: 'mouseenter',
  3283. pointerdown: 'mousedown',
  3284. pointermove: 'mousemove',
  3285. pointerup: 'mouseup',
  3286. pointerleave: 'mouseout',
  3287. pointerout: 'mouseout'
  3288. };
  3289. const isNullOrEmpty = value => value === null || value === '';
  3290. function initCanvas(canvas, aspectRatio) {
  3291. const style = canvas.style;
  3292. const renderHeight = canvas.getAttribute('height');
  3293. const renderWidth = canvas.getAttribute('width');
  3294. canvas[EXPANDO_KEY] = {
  3295. initial: {
  3296. height: renderHeight,
  3297. width: renderWidth,
  3298. style: {
  3299. display: style.display,
  3300. height: style.height,
  3301. width: style.width
  3302. }
  3303. }
  3304. };
  3305. style.display = style.display || 'block';
  3306. style.boxSizing = style.boxSizing || 'border-box';
  3307. if (isNullOrEmpty(renderWidth)) {
  3308. const displayWidth = readUsedSize(canvas, 'width');
  3309. if (displayWidth !== undefined) {
  3310. canvas.width = displayWidth;
  3311. }
  3312. }
  3313. if (isNullOrEmpty(renderHeight)) {
  3314. if (canvas.style.height === '') {
  3315. canvas.height = canvas.width / (aspectRatio || 2);
  3316. } else {
  3317. const displayHeight = readUsedSize(canvas, 'height');
  3318. if (displayHeight !== undefined) {
  3319. canvas.height = displayHeight;
  3320. }
  3321. }
  3322. }
  3323. return canvas;
  3324. }
  3325. const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
  3326. function addListener(node, type, listener) {
  3327. node.addEventListener(type, listener, eventListenerOptions);
  3328. }
  3329. function removeListener(chart, type, listener) {
  3330. chart.canvas.removeEventListener(type, listener, eventListenerOptions);
  3331. }
  3332. function fromNativeEvent(event, chart) {
  3333. const type = EVENT_TYPES[event.type] || event.type;
  3334. const {x, y} = getRelativePosition(event, chart);
  3335. return {
  3336. type,
  3337. chart,
  3338. native: event,
  3339. x: x !== undefined ? x : null,
  3340. y: y !== undefined ? y : null,
  3341. };
  3342. }
  3343. function nodeListContains(nodeList, canvas) {
  3344. for (const node of nodeList) {
  3345. if (node === canvas || node.contains(canvas)) {
  3346. return true;
  3347. }
  3348. }
  3349. }
  3350. function createAttachObserver(chart, type, listener) {
  3351. const canvas = chart.canvas;
  3352. const observer = new MutationObserver(entries => {
  3353. let trigger = false;
  3354. for (const entry of entries) {
  3355. trigger = trigger || nodeListContains(entry.addedNodes, canvas);
  3356. trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
  3357. }
  3358. if (trigger) {
  3359. listener();
  3360. }
  3361. });
  3362. observer.observe(document, {childList: true, subtree: true});
  3363. return observer;
  3364. }
  3365. function createDetachObserver(chart, type, listener) {
  3366. const canvas = chart.canvas;
  3367. const observer = new MutationObserver(entries => {
  3368. let trigger = false;
  3369. for (const entry of entries) {
  3370. trigger = trigger || nodeListContains(entry.removedNodes, canvas);
  3371. trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
  3372. }
  3373. if (trigger) {
  3374. listener();
  3375. }
  3376. });
  3377. observer.observe(document, {childList: true, subtree: true});
  3378. return observer;
  3379. }
  3380. const drpListeningCharts = new Map();
  3381. let oldDevicePixelRatio = 0;
  3382. function onWindowResize() {
  3383. const dpr = window.devicePixelRatio;
  3384. if (dpr === oldDevicePixelRatio) {
  3385. return;
  3386. }
  3387. oldDevicePixelRatio = dpr;
  3388. drpListeningCharts.forEach((resize, chart) => {
  3389. if (chart.currentDevicePixelRatio !== dpr) {
  3390. resize();
  3391. }
  3392. });
  3393. }
  3394. function listenDevicePixelRatioChanges(chart, resize) {
  3395. if (!drpListeningCharts.size) {
  3396. window.addEventListener('resize', onWindowResize);
  3397. }
  3398. drpListeningCharts.set(chart, resize);
  3399. }
  3400. function unlistenDevicePixelRatioChanges(chart) {
  3401. drpListeningCharts.delete(chart);
  3402. if (!drpListeningCharts.size) {
  3403. window.removeEventListener('resize', onWindowResize);
  3404. }
  3405. }
  3406. function createResizeObserver(chart, type, listener) {
  3407. const canvas = chart.canvas;
  3408. const container = canvas && _getParentNode(canvas);
  3409. if (!container) {
  3410. return;
  3411. }
  3412. const resize = throttled((width, height) => {
  3413. const w = container.clientWidth;
  3414. listener(width, height);
  3415. if (w < container.clientWidth) {
  3416. listener();
  3417. }
  3418. }, window);
  3419. const observer = new ResizeObserver(entries => {
  3420. const entry = entries[0];
  3421. const width = entry.contentRect.width;
  3422. const height = entry.contentRect.height;
  3423. if (width === 0 && height === 0) {
  3424. return;
  3425. }
  3426. resize(width, height);
  3427. });
  3428. observer.observe(container);
  3429. listenDevicePixelRatioChanges(chart, resize);
  3430. return observer;
  3431. }
  3432. function releaseObserver(chart, type, observer) {
  3433. if (observer) {
  3434. observer.disconnect();
  3435. }
  3436. if (type === 'resize') {
  3437. unlistenDevicePixelRatioChanges(chart);
  3438. }
  3439. }
  3440. function createProxyAndListen(chart, type, listener) {
  3441. const canvas = chart.canvas;
  3442. const proxy = throttled((event) => {
  3443. if (chart.ctx !== null) {
  3444. listener(fromNativeEvent(event, chart));
  3445. }
  3446. }, chart, (args) => {
  3447. const event = args[0];
  3448. return [event, event.offsetX, event.offsetY];
  3449. });
  3450. addListener(canvas, type, proxy);
  3451. return proxy;
  3452. }
  3453. class DomPlatform extends BasePlatform {
  3454. acquireContext(canvas, aspectRatio) {
  3455. const context = canvas && canvas.getContext && canvas.getContext('2d');
  3456. if (context && context.canvas === canvas) {
  3457. initCanvas(canvas, aspectRatio);
  3458. return context;
  3459. }
  3460. return null;
  3461. }
  3462. releaseContext(context) {
  3463. const canvas = context.canvas;
  3464. if (!canvas[EXPANDO_KEY]) {
  3465. return false;
  3466. }
  3467. const initial = canvas[EXPANDO_KEY].initial;
  3468. ['height', 'width'].forEach((prop) => {
  3469. const value = initial[prop];
  3470. if (isNullOrUndef(value)) {
  3471. canvas.removeAttribute(prop);
  3472. } else {
  3473. canvas.setAttribute(prop, value);
  3474. }
  3475. });
  3476. const style = initial.style || {};
  3477. Object.keys(style).forEach((key) => {
  3478. canvas.style[key] = style[key];
  3479. });
  3480. canvas.width = canvas.width;
  3481. delete canvas[EXPANDO_KEY];
  3482. return true;
  3483. }
  3484. addEventListener(chart, type, listener) {
  3485. this.removeEventListener(chart, type);
  3486. const proxies = chart.$proxies || (chart.$proxies = {});
  3487. const handlers = {
  3488. attach: createAttachObserver,
  3489. detach: createDetachObserver,
  3490. resize: createResizeObserver
  3491. };
  3492. const handler = handlers[type] || createProxyAndListen;
  3493. proxies[type] = handler(chart, type, listener);
  3494. }
  3495. removeEventListener(chart, type) {
  3496. const proxies = chart.$proxies || (chart.$proxies = {});
  3497. const proxy = proxies[type];
  3498. if (!proxy) {
  3499. return;
  3500. }
  3501. const handlers = {
  3502. attach: releaseObserver,
  3503. detach: releaseObserver,
  3504. resize: releaseObserver
  3505. };
  3506. const handler = handlers[type] || removeListener;
  3507. handler(chart, type, proxy);
  3508. proxies[type] = undefined;
  3509. }
  3510. getDevicePixelRatio() {
  3511. return window.devicePixelRatio;
  3512. }
  3513. getMaximumSize(canvas, width, height, aspectRatio) {
  3514. return getMaximumSize(canvas, width, height, aspectRatio);
  3515. }
  3516. isAttached(canvas) {
  3517. const container = _getParentNode(canvas);
  3518. return !!(container && container.isConnected);
  3519. }
  3520. }
  3521. function _detectPlatform(canvas) {
  3522. if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {
  3523. return BasicPlatform;
  3524. }
  3525. return DomPlatform;
  3526. }
  3527. var platforms = /*#__PURE__*/Object.freeze({
  3528. __proto__: null,
  3529. _detectPlatform: _detectPlatform,
  3530. BasePlatform: BasePlatform,
  3531. BasicPlatform: BasicPlatform,
  3532. DomPlatform: DomPlatform
  3533. });
  3534. const transparent = 'transparent';
  3535. const interpolators = {
  3536. boolean(from, to, factor) {
  3537. return factor > 0.5 ? to : from;
  3538. },
  3539. color(from, to, factor) {
  3540. const c0 = color(from || transparent);
  3541. const c1 = c0.valid && color(to || transparent);
  3542. return c1 && c1.valid
  3543. ? c1.mix(c0, factor).hexString()
  3544. : to;
  3545. },
  3546. number(from, to, factor) {
  3547. return from + (to - from) * factor;
  3548. }
  3549. };
  3550. class Animation {
  3551. constructor(cfg, target, prop, to) {
  3552. const currentValue = target[prop];
  3553. to = resolve([cfg.to, to, currentValue, cfg.from]);
  3554. const from = resolve([cfg.from, currentValue, to]);
  3555. this._active = true;
  3556. this._fn = cfg.fn || interpolators[cfg.type || typeof from];
  3557. this._easing = effects[cfg.easing] || effects.linear;
  3558. this._start = Math.floor(Date.now() + (cfg.delay || 0));
  3559. this._duration = this._total = Math.floor(cfg.duration);
  3560. this._loop = !!cfg.loop;
  3561. this._target = target;
  3562. this._prop = prop;
  3563. this._from = from;
  3564. this._to = to;
  3565. this._promises = undefined;
  3566. }
  3567. active() {
  3568. return this._active;
  3569. }
  3570. update(cfg, to, date) {
  3571. if (this._active) {
  3572. this._notify(false);
  3573. const currentValue = this._target[this._prop];
  3574. const elapsed = date - this._start;
  3575. const remain = this._duration - elapsed;
  3576. this._start = date;
  3577. this._duration = Math.floor(Math.max(remain, cfg.duration));
  3578. this._total += elapsed;
  3579. this._loop = !!cfg.loop;
  3580. this._to = resolve([cfg.to, to, currentValue, cfg.from]);
  3581. this._from = resolve([cfg.from, currentValue, to]);
  3582. }
  3583. }
  3584. cancel() {
  3585. if (this._active) {
  3586. this.tick(Date.now());
  3587. this._active = false;
  3588. this._notify(false);
  3589. }
  3590. }
  3591. tick(date) {
  3592. const elapsed = date - this._start;
  3593. const duration = this._duration;
  3594. const prop = this._prop;
  3595. const from = this._from;
  3596. const loop = this._loop;
  3597. const to = this._to;
  3598. let factor;
  3599. this._active = from !== to && (loop || (elapsed < duration));
  3600. if (!this._active) {
  3601. this._target[prop] = to;
  3602. this._notify(true);
  3603. return;
  3604. }
  3605. if (elapsed < 0) {
  3606. this._target[prop] = from;
  3607. return;
  3608. }
  3609. factor = (elapsed / duration) % 2;
  3610. factor = loop && factor > 1 ? 2 - factor : factor;
  3611. factor = this._easing(Math.min(1, Math.max(0, factor)));
  3612. this._target[prop] = this._fn(from, to, factor);
  3613. }
  3614. wait() {
  3615. const promises = this._promises || (this._promises = []);
  3616. return new Promise((res, rej) => {
  3617. promises.push({res, rej});
  3618. });
  3619. }
  3620. _notify(resolved) {
  3621. const method = resolved ? 'res' : 'rej';
  3622. const promises = this._promises || [];
  3623. for (let i = 0; i < promises.length; i++) {
  3624. promises[i][method]();
  3625. }
  3626. }
  3627. }
  3628. const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
  3629. const colors = ['color', 'borderColor', 'backgroundColor'];
  3630. defaults.set('animation', {
  3631. delay: undefined,
  3632. duration: 1000,
  3633. easing: 'easeOutQuart',
  3634. fn: undefined,
  3635. from: undefined,
  3636. loop: undefined,
  3637. to: undefined,
  3638. type: undefined,
  3639. });
  3640. const animationOptions = Object.keys(defaults.animation);
  3641. defaults.describe('animation', {
  3642. _fallback: false,
  3643. _indexable: false,
  3644. _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
  3645. });
  3646. defaults.set('animations', {
  3647. colors: {
  3648. type: 'color',
  3649. properties: colors
  3650. },
  3651. numbers: {
  3652. type: 'number',
  3653. properties: numbers
  3654. },
  3655. });
  3656. defaults.describe('animations', {
  3657. _fallback: 'animation',
  3658. });
  3659. defaults.set('transitions', {
  3660. active: {
  3661. animation: {
  3662. duration: 400
  3663. }
  3664. },
  3665. resize: {
  3666. animation: {
  3667. duration: 0
  3668. }
  3669. },
  3670. show: {
  3671. animations: {
  3672. colors: {
  3673. from: 'transparent'
  3674. },
  3675. visible: {
  3676. type: 'boolean',
  3677. duration: 0
  3678. },
  3679. }
  3680. },
  3681. hide: {
  3682. animations: {
  3683. colors: {
  3684. to: 'transparent'
  3685. },
  3686. visible: {
  3687. type: 'boolean',
  3688. easing: 'linear',
  3689. fn: v => v | 0
  3690. },
  3691. }
  3692. }
  3693. });
  3694. class Animations {
  3695. constructor(chart, config) {
  3696. this._chart = chart;
  3697. this._properties = new Map();
  3698. this.configure(config);
  3699. }
  3700. configure(config) {
  3701. if (!isObject(config)) {
  3702. return;
  3703. }
  3704. const animatedProps = this._properties;
  3705. Object.getOwnPropertyNames(config).forEach(key => {
  3706. const cfg = config[key];
  3707. if (!isObject(cfg)) {
  3708. return;
  3709. }
  3710. const resolved = {};
  3711. for (const option of animationOptions) {
  3712. resolved[option] = cfg[option];
  3713. }
  3714. (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {
  3715. if (prop === key || !animatedProps.has(prop)) {
  3716. animatedProps.set(prop, resolved);
  3717. }
  3718. });
  3719. });
  3720. }
  3721. _animateOptions(target, values) {
  3722. const newOptions = values.options;
  3723. const options = resolveTargetOptions(target, newOptions);
  3724. if (!options) {
  3725. return [];
  3726. }
  3727. const animations = this._createAnimations(options, newOptions);
  3728. if (newOptions.$shared) {
  3729. awaitAll(target.options.$animations, newOptions).then(() => {
  3730. target.options = newOptions;
  3731. }, () => {
  3732. });
  3733. }
  3734. return animations;
  3735. }
  3736. _createAnimations(target, values) {
  3737. const animatedProps = this._properties;
  3738. const animations = [];
  3739. const running = target.$animations || (target.$animations = {});
  3740. const props = Object.keys(values);
  3741. const date = Date.now();
  3742. let i;
  3743. for (i = props.length - 1; i >= 0; --i) {
  3744. const prop = props[i];
  3745. if (prop.charAt(0) === '$') {
  3746. continue;
  3747. }
  3748. if (prop === 'options') {
  3749. animations.push(...this._animateOptions(target, values));
  3750. continue;
  3751. }
  3752. const value = values[prop];
  3753. let animation = running[prop];
  3754. const cfg = animatedProps.get(prop);
  3755. if (animation) {
  3756. if (cfg && animation.active()) {
  3757. animation.update(cfg, value, date);
  3758. continue;
  3759. } else {
  3760. animation.cancel();
  3761. }
  3762. }
  3763. if (!cfg || !cfg.duration) {
  3764. target[prop] = value;
  3765. continue;
  3766. }
  3767. running[prop] = animation = new Animation(cfg, target, prop, value);
  3768. animations.push(animation);
  3769. }
  3770. return animations;
  3771. }
  3772. update(target, values) {
  3773. if (this._properties.size === 0) {
  3774. Object.assign(target, values);
  3775. return;
  3776. }
  3777. const animations = this._createAnimations(target, values);
  3778. if (animations.length) {
  3779. animator.add(this._chart, animations);
  3780. return true;
  3781. }
  3782. }
  3783. }
  3784. function awaitAll(animations, properties) {
  3785. const running = [];
  3786. const keys = Object.keys(properties);
  3787. for (let i = 0; i < keys.length; i++) {
  3788. const anim = animations[keys[i]];
  3789. if (anim && anim.active()) {
  3790. running.push(anim.wait());
  3791. }
  3792. }
  3793. return Promise.all(running);
  3794. }
  3795. function resolveTargetOptions(target, newOptions) {
  3796. if (!newOptions) {
  3797. return;
  3798. }
  3799. let options = target.options;
  3800. if (!options) {
  3801. target.options = newOptions;
  3802. return;
  3803. }
  3804. if (options.$shared) {
  3805. target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
  3806. }
  3807. return options;
  3808. }
  3809. function scaleClip(scale, allowedOverflow) {
  3810. const opts = scale && scale.options || {};
  3811. const reverse = opts.reverse;
  3812. const min = opts.min === undefined ? allowedOverflow : 0;
  3813. const max = opts.max === undefined ? allowedOverflow : 0;
  3814. return {
  3815. start: reverse ? max : min,
  3816. end: reverse ? min : max
  3817. };
  3818. }
  3819. function defaultClip(xScale, yScale, allowedOverflow) {
  3820. if (allowedOverflow === false) {
  3821. return false;
  3822. }
  3823. const x = scaleClip(xScale, allowedOverflow);
  3824. const y = scaleClip(yScale, allowedOverflow);
  3825. return {
  3826. top: y.end,
  3827. right: x.end,
  3828. bottom: y.start,
  3829. left: x.start
  3830. };
  3831. }
  3832. function toClip(value) {
  3833. let t, r, b, l;
  3834. if (isObject(value)) {
  3835. t = value.top;
  3836. r = value.right;
  3837. b = value.bottom;
  3838. l = value.left;
  3839. } else {
  3840. t = r = b = l = value;
  3841. }
  3842. return {
  3843. top: t,
  3844. right: r,
  3845. bottom: b,
  3846. left: l,
  3847. disabled: value === false
  3848. };
  3849. }
  3850. function getSortedDatasetIndices(chart, filterVisible) {
  3851. const keys = [];
  3852. const metasets = chart._getSortedDatasetMetas(filterVisible);
  3853. let i, ilen;
  3854. for (i = 0, ilen = metasets.length; i < ilen; ++i) {
  3855. keys.push(metasets[i].index);
  3856. }
  3857. return keys;
  3858. }
  3859. function applyStack(stack, value, dsIndex, options = {}) {
  3860. const keys = stack.keys;
  3861. const singleMode = options.mode === 'single';
  3862. let i, ilen, datasetIndex, otherValue;
  3863. if (value === null) {
  3864. return;
  3865. }
  3866. for (i = 0, ilen = keys.length; i < ilen; ++i) {
  3867. datasetIndex = +keys[i];
  3868. if (datasetIndex === dsIndex) {
  3869. if (options.all) {
  3870. continue;
  3871. }
  3872. break;
  3873. }
  3874. otherValue = stack.values[datasetIndex];
  3875. if (isNumberFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {
  3876. value += otherValue;
  3877. }
  3878. }
  3879. return value;
  3880. }
  3881. function convertObjectDataToArray(data) {
  3882. const keys = Object.keys(data);
  3883. const adata = new Array(keys.length);
  3884. let i, ilen, key;
  3885. for (i = 0, ilen = keys.length; i < ilen; ++i) {
  3886. key = keys[i];
  3887. adata[i] = {
  3888. x: key,
  3889. y: data[key]
  3890. };
  3891. }
  3892. return adata;
  3893. }
  3894. function isStacked(scale, meta) {
  3895. const stacked = scale && scale.options.stacked;
  3896. return stacked || (stacked === undefined && meta.stack !== undefined);
  3897. }
  3898. function getStackKey(indexScale, valueScale, meta) {
  3899. return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
  3900. }
  3901. function getUserBounds(scale) {
  3902. const {min, max, minDefined, maxDefined} = scale.getUserBounds();
  3903. return {
  3904. min: minDefined ? min : Number.NEGATIVE_INFINITY,
  3905. max: maxDefined ? max : Number.POSITIVE_INFINITY
  3906. };
  3907. }
  3908. function getOrCreateStack(stacks, stackKey, indexValue) {
  3909. const subStack = stacks[stackKey] || (stacks[stackKey] = {});
  3910. return subStack[indexValue] || (subStack[indexValue] = {});
  3911. }
  3912. function getLastIndexInStack(stack, vScale, positive, type) {
  3913. for (const meta of vScale.getMatchingVisibleMetas(type).reverse()) {
  3914. const value = stack[meta.index];
  3915. if ((positive && value > 0) || (!positive && value < 0)) {
  3916. return meta.index;
  3917. }
  3918. }
  3919. return null;
  3920. }
  3921. function updateStacks(controller, parsed) {
  3922. const {chart, _cachedMeta: meta} = controller;
  3923. const stacks = chart._stacks || (chart._stacks = {});
  3924. const {iScale, vScale, index: datasetIndex} = meta;
  3925. const iAxis = iScale.axis;
  3926. const vAxis = vScale.axis;
  3927. const key = getStackKey(iScale, vScale, meta);
  3928. const ilen = parsed.length;
  3929. let stack;
  3930. for (let i = 0; i < ilen; ++i) {
  3931. const item = parsed[i];
  3932. const {[iAxis]: index, [vAxis]: value} = item;
  3933. const itemStacks = item._stacks || (item._stacks = {});
  3934. stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
  3935. stack[datasetIndex] = value;
  3936. stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
  3937. stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
  3938. }
  3939. }
  3940. function getFirstScaleId(chart, axis) {
  3941. const scales = chart.scales;
  3942. return Object.keys(scales).filter(key => scales[key].axis === axis).shift();
  3943. }
  3944. function createDatasetContext(parent, index) {
  3945. return createContext(parent,
  3946. {
  3947. active: false,
  3948. dataset: undefined,
  3949. datasetIndex: index,
  3950. index,
  3951. mode: 'default',
  3952. type: 'dataset'
  3953. }
  3954. );
  3955. }
  3956. function createDataContext(parent, index, element) {
  3957. return createContext(parent, {
  3958. active: false,
  3959. dataIndex: index,
  3960. parsed: undefined,
  3961. raw: undefined,
  3962. element,
  3963. index,
  3964. mode: 'default',
  3965. type: 'data'
  3966. });
  3967. }
  3968. function clearStacks(meta, items) {
  3969. const datasetIndex = meta.controller.index;
  3970. const axis = meta.vScale && meta.vScale.axis;
  3971. if (!axis) {
  3972. return;
  3973. }
  3974. items = items || meta._parsed;
  3975. for (const parsed of items) {
  3976. const stacks = parsed._stacks;
  3977. if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
  3978. return;
  3979. }
  3980. delete stacks[axis][datasetIndex];
  3981. }
  3982. }
  3983. const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
  3984. const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);
  3985. const createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked
  3986. && {keys: getSortedDatasetIndices(chart, true), values: null};
  3987. class DatasetController {
  3988. constructor(chart, datasetIndex) {
  3989. this.chart = chart;
  3990. this._ctx = chart.ctx;
  3991. this.index = datasetIndex;
  3992. this._cachedDataOpts = {};
  3993. this._cachedMeta = this.getMeta();
  3994. this._type = this._cachedMeta.type;
  3995. this.options = undefined;
  3996. this._parsing = false;
  3997. this._data = undefined;
  3998. this._objectData = undefined;
  3999. this._sharedOptions = undefined;
  4000. this._drawStart = undefined;
  4001. this._drawCount = undefined;
  4002. this.enableOptionSharing = false;
  4003. this.supportsDecimation = false;
  4004. this.$context = undefined;
  4005. this._syncList = [];
  4006. this.initialize();
  4007. }
  4008. initialize() {
  4009. const meta = this._cachedMeta;
  4010. this.configure();
  4011. this.linkScales();
  4012. meta._stacked = isStacked(meta.vScale, meta);
  4013. this.addElements();
  4014. }
  4015. updateIndex(datasetIndex) {
  4016. if (this.index !== datasetIndex) {
  4017. clearStacks(this._cachedMeta);
  4018. }
  4019. this.index = datasetIndex;
  4020. }
  4021. linkScales() {
  4022. const chart = this.chart;
  4023. const meta = this._cachedMeta;
  4024. const dataset = this.getDataset();
  4025. const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y;
  4026. const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
  4027. const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
  4028. const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
  4029. const indexAxis = meta.indexAxis;
  4030. const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
  4031. const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
  4032. meta.xScale = this.getScaleForId(xid);
  4033. meta.yScale = this.getScaleForId(yid);
  4034. meta.rScale = this.getScaleForId(rid);
  4035. meta.iScale = this.getScaleForId(iid);
  4036. meta.vScale = this.getScaleForId(vid);
  4037. }
  4038. getDataset() {
  4039. return this.chart.data.datasets[this.index];
  4040. }
  4041. getMeta() {
  4042. return this.chart.getDatasetMeta(this.index);
  4043. }
  4044. getScaleForId(scaleID) {
  4045. return this.chart.scales[scaleID];
  4046. }
  4047. _getOtherScale(scale) {
  4048. const meta = this._cachedMeta;
  4049. return scale === meta.iScale
  4050. ? meta.vScale
  4051. : meta.iScale;
  4052. }
  4053. reset() {
  4054. this._update('reset');
  4055. }
  4056. _destroy() {
  4057. const meta = this._cachedMeta;
  4058. if (this._data) {
  4059. unlistenArrayEvents(this._data, this);
  4060. }
  4061. if (meta._stacked) {
  4062. clearStacks(meta);
  4063. }
  4064. }
  4065. _dataCheck() {
  4066. const dataset = this.getDataset();
  4067. const data = dataset.data || (dataset.data = []);
  4068. const _data = this._data;
  4069. if (isObject(data)) {
  4070. this._data = convertObjectDataToArray(data);
  4071. } else if (_data !== data) {
  4072. if (_data) {
  4073. unlistenArrayEvents(_data, this);
  4074. const meta = this._cachedMeta;
  4075. clearStacks(meta);
  4076. meta._parsed = [];
  4077. }
  4078. if (data && Object.isExtensible(data)) {
  4079. listenArrayEvents(data, this);
  4080. }
  4081. this._syncList = [];
  4082. this._data = data;
  4083. }
  4084. }
  4085. addElements() {
  4086. const meta = this._cachedMeta;
  4087. this._dataCheck();
  4088. if (this.datasetElementType) {
  4089. meta.dataset = new this.datasetElementType();
  4090. }
  4091. }
  4092. buildOrUpdateElements(resetNewElements) {
  4093. const meta = this._cachedMeta;
  4094. const dataset = this.getDataset();
  4095. let stackChanged = false;
  4096. this._dataCheck();
  4097. const oldStacked = meta._stacked;
  4098. meta._stacked = isStacked(meta.vScale, meta);
  4099. if (meta.stack !== dataset.stack) {
  4100. stackChanged = true;
  4101. clearStacks(meta);
  4102. meta.stack = dataset.stack;
  4103. }
  4104. this._resyncElements(resetNewElements);
  4105. if (stackChanged || oldStacked !== meta._stacked) {
  4106. updateStacks(this, meta._parsed);
  4107. }
  4108. }
  4109. configure() {
  4110. const config = this.chart.config;
  4111. const scopeKeys = config.datasetScopeKeys(this._type);
  4112. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
  4113. this.options = config.createResolver(scopes, this.getContext());
  4114. this._parsing = this.options.parsing;
  4115. this._cachedDataOpts = {};
  4116. }
  4117. parse(start, count) {
  4118. const {_cachedMeta: meta, _data: data} = this;
  4119. const {iScale, _stacked} = meta;
  4120. const iAxis = iScale.axis;
  4121. let sorted = start === 0 && count === data.length ? true : meta._sorted;
  4122. let prev = start > 0 && meta._parsed[start - 1];
  4123. let i, cur, parsed;
  4124. if (this._parsing === false) {
  4125. meta._parsed = data;
  4126. meta._sorted = true;
  4127. parsed = data;
  4128. } else {
  4129. if (isArray(data[start])) {
  4130. parsed = this.parseArrayData(meta, data, start, count);
  4131. } else if (isObject(data[start])) {
  4132. parsed = this.parseObjectData(meta, data, start, count);
  4133. } else {
  4134. parsed = this.parsePrimitiveData(meta, data, start, count);
  4135. }
  4136. const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);
  4137. for (i = 0; i < count; ++i) {
  4138. meta._parsed[i + start] = cur = parsed[i];
  4139. if (sorted) {
  4140. if (isNotInOrderComparedToPrev()) {
  4141. sorted = false;
  4142. }
  4143. prev = cur;
  4144. }
  4145. }
  4146. meta._sorted = sorted;
  4147. }
  4148. if (_stacked) {
  4149. updateStacks(this, parsed);
  4150. }
  4151. }
  4152. parsePrimitiveData(meta, data, start, count) {
  4153. const {iScale, vScale} = meta;
  4154. const iAxis = iScale.axis;
  4155. const vAxis = vScale.axis;
  4156. const labels = iScale.getLabels();
  4157. const singleScale = iScale === vScale;
  4158. const parsed = new Array(count);
  4159. let i, ilen, index;
  4160. for (i = 0, ilen = count; i < ilen; ++i) {
  4161. index = i + start;
  4162. parsed[i] = {
  4163. [iAxis]: singleScale || iScale.parse(labels[index], index),
  4164. [vAxis]: vScale.parse(data[index], index)
  4165. };
  4166. }
  4167. return parsed;
  4168. }
  4169. parseArrayData(meta, data, start, count) {
  4170. const {xScale, yScale} = meta;
  4171. const parsed = new Array(count);
  4172. let i, ilen, index, item;
  4173. for (i = 0, ilen = count; i < ilen; ++i) {
  4174. index = i + start;
  4175. item = data[index];
  4176. parsed[i] = {
  4177. x: xScale.parse(item[0], index),
  4178. y: yScale.parse(item[1], index)
  4179. };
  4180. }
  4181. return parsed;
  4182. }
  4183. parseObjectData(meta, data, start, count) {
  4184. const {xScale, yScale} = meta;
  4185. const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
  4186. const parsed = new Array(count);
  4187. let i, ilen, index, item;
  4188. for (i = 0, ilen = count; i < ilen; ++i) {
  4189. index = i + start;
  4190. item = data[index];
  4191. parsed[i] = {
  4192. x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
  4193. y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
  4194. };
  4195. }
  4196. return parsed;
  4197. }
  4198. getParsed(index) {
  4199. return this._cachedMeta._parsed[index];
  4200. }
  4201. getDataElement(index) {
  4202. return this._cachedMeta.data[index];
  4203. }
  4204. applyStack(scale, parsed, mode) {
  4205. const chart = this.chart;
  4206. const meta = this._cachedMeta;
  4207. const value = parsed[scale.axis];
  4208. const stack = {
  4209. keys: getSortedDatasetIndices(chart, true),
  4210. values: parsed._stacks[scale.axis]
  4211. };
  4212. return applyStack(stack, value, meta.index, {mode});
  4213. }
  4214. updateRangeFromParsed(range, scale, parsed, stack) {
  4215. const parsedValue = parsed[scale.axis];
  4216. let value = parsedValue === null ? NaN : parsedValue;
  4217. const values = stack && parsed._stacks[scale.axis];
  4218. if (stack && values) {
  4219. stack.values = values;
  4220. value = applyStack(stack, parsedValue, this._cachedMeta.index);
  4221. }
  4222. range.min = Math.min(range.min, value);
  4223. range.max = Math.max(range.max, value);
  4224. }
  4225. getMinMax(scale, canStack) {
  4226. const meta = this._cachedMeta;
  4227. const _parsed = meta._parsed;
  4228. const sorted = meta._sorted && scale === meta.iScale;
  4229. const ilen = _parsed.length;
  4230. const otherScale = this._getOtherScale(scale);
  4231. const stack = createStack(canStack, meta, this.chart);
  4232. const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};
  4233. const {min: otherMin, max: otherMax} = getUserBounds(otherScale);
  4234. let i, parsed;
  4235. function _skip() {
  4236. parsed = _parsed[i];
  4237. const otherValue = parsed[otherScale.axis];
  4238. return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
  4239. }
  4240. for (i = 0; i < ilen; ++i) {
  4241. if (_skip()) {
  4242. continue;
  4243. }
  4244. this.updateRangeFromParsed(range, scale, parsed, stack);
  4245. if (sorted) {
  4246. break;
  4247. }
  4248. }
  4249. if (sorted) {
  4250. for (i = ilen - 1; i >= 0; --i) {
  4251. if (_skip()) {
  4252. continue;
  4253. }
  4254. this.updateRangeFromParsed(range, scale, parsed, stack);
  4255. break;
  4256. }
  4257. }
  4258. return range;
  4259. }
  4260. getAllParsedValues(scale) {
  4261. const parsed = this._cachedMeta._parsed;
  4262. const values = [];
  4263. let i, ilen, value;
  4264. for (i = 0, ilen = parsed.length; i < ilen; ++i) {
  4265. value = parsed[i][scale.axis];
  4266. if (isNumberFinite(value)) {
  4267. values.push(value);
  4268. }
  4269. }
  4270. return values;
  4271. }
  4272. getMaxOverflow() {
  4273. return false;
  4274. }
  4275. getLabelAndValue(index) {
  4276. const meta = this._cachedMeta;
  4277. const iScale = meta.iScale;
  4278. const vScale = meta.vScale;
  4279. const parsed = this.getParsed(index);
  4280. return {
  4281. label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
  4282. value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
  4283. };
  4284. }
  4285. _update(mode) {
  4286. const meta = this._cachedMeta;
  4287. this.update(mode || 'default');
  4288. meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
  4289. }
  4290. update(mode) {}
  4291. draw() {
  4292. const ctx = this._ctx;
  4293. const chart = this.chart;
  4294. const meta = this._cachedMeta;
  4295. const elements = meta.data || [];
  4296. const area = chart.chartArea;
  4297. const active = [];
  4298. const start = this._drawStart || 0;
  4299. const count = this._drawCount || (elements.length - start);
  4300. const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
  4301. let i;
  4302. if (meta.dataset) {
  4303. meta.dataset.draw(ctx, area, start, count);
  4304. }
  4305. for (i = start; i < start + count; ++i) {
  4306. const element = elements[i];
  4307. if (element.hidden) {
  4308. continue;
  4309. }
  4310. if (element.active && drawActiveElementsOnTop) {
  4311. active.push(element);
  4312. } else {
  4313. element.draw(ctx, area);
  4314. }
  4315. }
  4316. for (i = 0; i < active.length; ++i) {
  4317. active[i].draw(ctx, area);
  4318. }
  4319. }
  4320. getStyle(index, active) {
  4321. const mode = active ? 'active' : 'default';
  4322. return index === undefined && this._cachedMeta.dataset
  4323. ? this.resolveDatasetElementOptions(mode)
  4324. : this.resolveDataElementOptions(index || 0, mode);
  4325. }
  4326. getContext(index, active, mode) {
  4327. const dataset = this.getDataset();
  4328. let context;
  4329. if (index >= 0 && index < this._cachedMeta.data.length) {
  4330. const element = this._cachedMeta.data[index];
  4331. context = element.$context ||
  4332. (element.$context = createDataContext(this.getContext(), index, element));
  4333. context.parsed = this.getParsed(index);
  4334. context.raw = dataset.data[index];
  4335. context.index = context.dataIndex = index;
  4336. } else {
  4337. context = this.$context ||
  4338. (this.$context = createDatasetContext(this.chart.getContext(), this.index));
  4339. context.dataset = dataset;
  4340. context.index = context.datasetIndex = this.index;
  4341. }
  4342. context.active = !!active;
  4343. context.mode = mode;
  4344. return context;
  4345. }
  4346. resolveDatasetElementOptions(mode) {
  4347. return this._resolveElementOptions(this.datasetElementType.id, mode);
  4348. }
  4349. resolveDataElementOptions(index, mode) {
  4350. return this._resolveElementOptions(this.dataElementType.id, mode, index);
  4351. }
  4352. _resolveElementOptions(elementType, mode = 'default', index) {
  4353. const active = mode === 'active';
  4354. const cache = this._cachedDataOpts;
  4355. const cacheKey = elementType + '-' + mode;
  4356. const cached = cache[cacheKey];
  4357. const sharing = this.enableOptionSharing && defined(index);
  4358. if (cached) {
  4359. return cloneIfNotShared(cached, sharing);
  4360. }
  4361. const config = this.chart.config;
  4362. const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
  4363. const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, ''];
  4364. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  4365. const names = Object.keys(defaults.elements[elementType]);
  4366. const context = () => this.getContext(index, active);
  4367. const values = config.resolveNamedOptions(scopes, names, context, prefixes);
  4368. if (values.$shared) {
  4369. values.$shared = sharing;
  4370. cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
  4371. }
  4372. return values;
  4373. }
  4374. _resolveAnimations(index, transition, active) {
  4375. const chart = this.chart;
  4376. const cache = this._cachedDataOpts;
  4377. const cacheKey = `animation-${transition}`;
  4378. const cached = cache[cacheKey];
  4379. if (cached) {
  4380. return cached;
  4381. }
  4382. let options;
  4383. if (chart.options.animation !== false) {
  4384. const config = this.chart.config;
  4385. const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
  4386. const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
  4387. options = config.createResolver(scopes, this.getContext(index, active, transition));
  4388. }
  4389. const animations = new Animations(chart, options && options.animations);
  4390. if (options && options._cacheable) {
  4391. cache[cacheKey] = Object.freeze(animations);
  4392. }
  4393. return animations;
  4394. }
  4395. getSharedOptions(options) {
  4396. if (!options.$shared) {
  4397. return;
  4398. }
  4399. return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
  4400. }
  4401. includeOptions(mode, sharedOptions) {
  4402. return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
  4403. }
  4404. updateElement(element, index, properties, mode) {
  4405. if (isDirectUpdateMode(mode)) {
  4406. Object.assign(element, properties);
  4407. } else {
  4408. this._resolveAnimations(index, mode).update(element, properties);
  4409. }
  4410. }
  4411. updateSharedOptions(sharedOptions, mode, newOptions) {
  4412. if (sharedOptions && !isDirectUpdateMode(mode)) {
  4413. this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
  4414. }
  4415. }
  4416. _setStyle(element, index, mode, active) {
  4417. element.active = active;
  4418. const options = this.getStyle(index, active);
  4419. this._resolveAnimations(index, mode, active).update(element, {
  4420. options: (!active && this.getSharedOptions(options)) || options
  4421. });
  4422. }
  4423. removeHoverStyle(element, datasetIndex, index) {
  4424. this._setStyle(element, index, 'active', false);
  4425. }
  4426. setHoverStyle(element, datasetIndex, index) {
  4427. this._setStyle(element, index, 'active', true);
  4428. }
  4429. _removeDatasetHoverStyle() {
  4430. const element = this._cachedMeta.dataset;
  4431. if (element) {
  4432. this._setStyle(element, undefined, 'active', false);
  4433. }
  4434. }
  4435. _setDatasetHoverStyle() {
  4436. const element = this._cachedMeta.dataset;
  4437. if (element) {
  4438. this._setStyle(element, undefined, 'active', true);
  4439. }
  4440. }
  4441. _resyncElements(resetNewElements) {
  4442. const data = this._data;
  4443. const elements = this._cachedMeta.data;
  4444. for (const [method, arg1, arg2] of this._syncList) {
  4445. this[method](arg1, arg2);
  4446. }
  4447. this._syncList = [];
  4448. const numMeta = elements.length;
  4449. const numData = data.length;
  4450. const count = Math.min(numData, numMeta);
  4451. if (count) {
  4452. this.parse(0, count);
  4453. }
  4454. if (numData > numMeta) {
  4455. this._insertElements(numMeta, numData - numMeta, resetNewElements);
  4456. } else if (numData < numMeta) {
  4457. this._removeElements(numData, numMeta - numData);
  4458. }
  4459. }
  4460. _insertElements(start, count, resetNewElements = true) {
  4461. const meta = this._cachedMeta;
  4462. const data = meta.data;
  4463. const end = start + count;
  4464. let i;
  4465. const move = (arr) => {
  4466. arr.length += count;
  4467. for (i = arr.length - 1; i >= end; i--) {
  4468. arr[i] = arr[i - count];
  4469. }
  4470. };
  4471. move(data);
  4472. for (i = start; i < end; ++i) {
  4473. data[i] = new this.dataElementType();
  4474. }
  4475. if (this._parsing) {
  4476. move(meta._parsed);
  4477. }
  4478. this.parse(start, count);
  4479. if (resetNewElements) {
  4480. this.updateElements(data, start, count, 'reset');
  4481. }
  4482. }
  4483. updateElements(element, start, count, mode) {}
  4484. _removeElements(start, count) {
  4485. const meta = this._cachedMeta;
  4486. if (this._parsing) {
  4487. const removed = meta._parsed.splice(start, count);
  4488. if (meta._stacked) {
  4489. clearStacks(meta, removed);
  4490. }
  4491. }
  4492. meta.data.splice(start, count);
  4493. }
  4494. _sync(args) {
  4495. if (this._parsing) {
  4496. this._syncList.push(args);
  4497. } else {
  4498. const [method, arg1, arg2] = args;
  4499. this[method](arg1, arg2);
  4500. }
  4501. this.chart._dataChanges.push([this.index, ...args]);
  4502. }
  4503. _onDataPush() {
  4504. const count = arguments.length;
  4505. this._sync(['_insertElements', this.getDataset().data.length - count, count]);
  4506. }
  4507. _onDataPop() {
  4508. this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]);
  4509. }
  4510. _onDataShift() {
  4511. this._sync(['_removeElements', 0, 1]);
  4512. }
  4513. _onDataSplice(start, count) {
  4514. if (count) {
  4515. this._sync(['_removeElements', start, count]);
  4516. }
  4517. const newCount = arguments.length - 2;
  4518. if (newCount) {
  4519. this._sync(['_insertElements', start, newCount]);
  4520. }
  4521. }
  4522. _onDataUnshift() {
  4523. this._sync(['_insertElements', 0, arguments.length]);
  4524. }
  4525. }
  4526. DatasetController.defaults = {};
  4527. DatasetController.prototype.datasetElementType = null;
  4528. DatasetController.prototype.dataElementType = null;
  4529. class Element {
  4530. constructor() {
  4531. this.x = undefined;
  4532. this.y = undefined;
  4533. this.active = false;
  4534. this.options = undefined;
  4535. this.$animations = undefined;
  4536. }
  4537. tooltipPosition(useFinalPosition) {
  4538. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  4539. return {x, y};
  4540. }
  4541. hasValue() {
  4542. return isNumber(this.x) && isNumber(this.y);
  4543. }
  4544. getProps(props, final) {
  4545. const anims = this.$animations;
  4546. if (!final || !anims) {
  4547. return this;
  4548. }
  4549. const ret = {};
  4550. props.forEach(prop => {
  4551. ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
  4552. });
  4553. return ret;
  4554. }
  4555. }
  4556. Element.defaults = {};
  4557. Element.defaultRoutes = undefined;
  4558. const formatters = {
  4559. values(value) {
  4560. return isArray(value) ? value : '' + value;
  4561. },
  4562. numeric(tickValue, index, ticks) {
  4563. if (tickValue === 0) {
  4564. return '0';
  4565. }
  4566. const locale = this.chart.options.locale;
  4567. let notation;
  4568. let delta = tickValue;
  4569. if (ticks.length > 1) {
  4570. const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
  4571. if (maxTick < 1e-4 || maxTick > 1e+15) {
  4572. notation = 'scientific';
  4573. }
  4574. delta = calculateDelta(tickValue, ticks);
  4575. }
  4576. const logDelta = log10(Math.abs(delta));
  4577. const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
  4578. const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal};
  4579. Object.assign(options, this.options.ticks.format);
  4580. return formatNumber(tickValue, locale, options);
  4581. },
  4582. logarithmic(tickValue, index, ticks) {
  4583. if (tickValue === 0) {
  4584. return '0';
  4585. }
  4586. const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue))));
  4587. if (remain === 1 || remain === 2 || remain === 5) {
  4588. return formatters.numeric.call(this, tickValue, index, ticks);
  4589. }
  4590. return '';
  4591. }
  4592. };
  4593. function calculateDelta(tickValue, ticks) {
  4594. let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
  4595. if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
  4596. delta = tickValue - Math.floor(tickValue);
  4597. }
  4598. return delta;
  4599. }
  4600. var Ticks = {formatters};
  4601. defaults.set('scale', {
  4602. display: true,
  4603. offset: false,
  4604. reverse: false,
  4605. beginAtZero: false,
  4606. bounds: 'ticks',
  4607. grace: 0,
  4608. grid: {
  4609. display: true,
  4610. lineWidth: 1,
  4611. drawBorder: true,
  4612. drawOnChartArea: true,
  4613. drawTicks: true,
  4614. tickLength: 8,
  4615. tickWidth: (_ctx, options) => options.lineWidth,
  4616. tickColor: (_ctx, options) => options.color,
  4617. offset: false,
  4618. borderDash: [],
  4619. borderDashOffset: 0.0,
  4620. borderWidth: 1
  4621. },
  4622. title: {
  4623. display: false,
  4624. text: '',
  4625. padding: {
  4626. top: 4,
  4627. bottom: 4
  4628. }
  4629. },
  4630. ticks: {
  4631. minRotation: 0,
  4632. maxRotation: 50,
  4633. mirror: false,
  4634. textStrokeWidth: 0,
  4635. textStrokeColor: '',
  4636. padding: 3,
  4637. display: true,
  4638. autoSkip: true,
  4639. autoSkipPadding: 3,
  4640. labelOffset: 0,
  4641. callback: Ticks.formatters.values,
  4642. minor: {},
  4643. major: {},
  4644. align: 'center',
  4645. crossAlign: 'near',
  4646. showLabelBackdrop: false,
  4647. backdropColor: 'rgba(255, 255, 255, 0.75)',
  4648. backdropPadding: 2,
  4649. }
  4650. });
  4651. defaults.route('scale.ticks', 'color', '', 'color');
  4652. defaults.route('scale.grid', 'color', '', 'borderColor');
  4653. defaults.route('scale.grid', 'borderColor', '', 'borderColor');
  4654. defaults.route('scale.title', 'color', '', 'color');
  4655. defaults.describe('scale', {
  4656. _fallback: false,
  4657. _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
  4658. _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
  4659. });
  4660. defaults.describe('scales', {
  4661. _fallback: 'scale',
  4662. });
  4663. defaults.describe('scale.ticks', {
  4664. _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback',
  4665. _indexable: (name) => name !== 'backdropPadding',
  4666. });
  4667. function autoSkip(scale, ticks) {
  4668. const tickOpts = scale.options.ticks;
  4669. const ticksLimit = tickOpts.maxTicksLimit || determineMaxTicks(scale);
  4670. const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
  4671. const numMajorIndices = majorIndices.length;
  4672. const first = majorIndices[0];
  4673. const last = majorIndices[numMajorIndices - 1];
  4674. const newTicks = [];
  4675. if (numMajorIndices > ticksLimit) {
  4676. skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
  4677. return newTicks;
  4678. }
  4679. const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
  4680. if (numMajorIndices > 0) {
  4681. let i, ilen;
  4682. const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
  4683. skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
  4684. for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
  4685. skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
  4686. }
  4687. skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
  4688. return newTicks;
  4689. }
  4690. skip(ticks, newTicks, spacing);
  4691. return newTicks;
  4692. }
  4693. function determineMaxTicks(scale) {
  4694. const offset = scale.options.offset;
  4695. const tickLength = scale._tickSize();
  4696. const maxScale = scale._length / tickLength + (offset ? 0 : 1);
  4697. const maxChart = scale._maxLength / tickLength;
  4698. return Math.floor(Math.min(maxScale, maxChart));
  4699. }
  4700. function calculateSpacing(majorIndices, ticks, ticksLimit) {
  4701. const evenMajorSpacing = getEvenSpacing(majorIndices);
  4702. const spacing = ticks.length / ticksLimit;
  4703. if (!evenMajorSpacing) {
  4704. return Math.max(spacing, 1);
  4705. }
  4706. const factors = _factorize(evenMajorSpacing);
  4707. for (let i = 0, ilen = factors.length - 1; i < ilen; i++) {
  4708. const factor = factors[i];
  4709. if (factor > spacing) {
  4710. return factor;
  4711. }
  4712. }
  4713. return Math.max(spacing, 1);
  4714. }
  4715. function getMajorIndices(ticks) {
  4716. const result = [];
  4717. let i, ilen;
  4718. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  4719. if (ticks[i].major) {
  4720. result.push(i);
  4721. }
  4722. }
  4723. return result;
  4724. }
  4725. function skipMajors(ticks, newTicks, majorIndices, spacing) {
  4726. let count = 0;
  4727. let next = majorIndices[0];
  4728. let i;
  4729. spacing = Math.ceil(spacing);
  4730. for (i = 0; i < ticks.length; i++) {
  4731. if (i === next) {
  4732. newTicks.push(ticks[i]);
  4733. count++;
  4734. next = majorIndices[count * spacing];
  4735. }
  4736. }
  4737. }
  4738. function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
  4739. const start = valueOrDefault(majorStart, 0);
  4740. const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
  4741. let count = 0;
  4742. let length, i, next;
  4743. spacing = Math.ceil(spacing);
  4744. if (majorEnd) {
  4745. length = majorEnd - majorStart;
  4746. spacing = length / Math.floor(length / spacing);
  4747. }
  4748. next = start;
  4749. while (next < 0) {
  4750. count++;
  4751. next = Math.round(start + count * spacing);
  4752. }
  4753. for (i = Math.max(start, 0); i < end; i++) {
  4754. if (i === next) {
  4755. newTicks.push(ticks[i]);
  4756. count++;
  4757. next = Math.round(start + count * spacing);
  4758. }
  4759. }
  4760. }
  4761. function getEvenSpacing(arr) {
  4762. const len = arr.length;
  4763. let i, diff;
  4764. if (len < 2) {
  4765. return false;
  4766. }
  4767. for (diff = arr[0], i = 1; i < len; ++i) {
  4768. if (arr[i] - arr[i - 1] !== diff) {
  4769. return false;
  4770. }
  4771. }
  4772. return diff;
  4773. }
  4774. const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  4775. const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  4776. function sample(arr, numItems) {
  4777. const result = [];
  4778. const increment = arr.length / numItems;
  4779. const len = arr.length;
  4780. let i = 0;
  4781. for (; i < len; i += increment) {
  4782. result.push(arr[Math.floor(i)]);
  4783. }
  4784. return result;
  4785. }
  4786. function getPixelForGridLine(scale, index, offsetGridLines) {
  4787. const length = scale.ticks.length;
  4788. const validIndex = Math.min(index, length - 1);
  4789. const start = scale._startPixel;
  4790. const end = scale._endPixel;
  4791. const epsilon = 1e-6;
  4792. let lineValue = scale.getPixelForTick(validIndex);
  4793. let offset;
  4794. if (offsetGridLines) {
  4795. if (length === 1) {
  4796. offset = Math.max(lineValue - start, end - lineValue);
  4797. } else if (index === 0) {
  4798. offset = (scale.getPixelForTick(1) - lineValue) / 2;
  4799. } else {
  4800. offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
  4801. }
  4802. lineValue += validIndex < index ? offset : -offset;
  4803. if (lineValue < start - epsilon || lineValue > end + epsilon) {
  4804. return;
  4805. }
  4806. }
  4807. return lineValue;
  4808. }
  4809. function garbageCollect(caches, length) {
  4810. each(caches, (cache) => {
  4811. const gc = cache.gc;
  4812. const gcLen = gc.length / 2;
  4813. let i;
  4814. if (gcLen > length) {
  4815. for (i = 0; i < gcLen; ++i) {
  4816. delete cache.data[gc[i]];
  4817. }
  4818. gc.splice(0, gcLen);
  4819. }
  4820. });
  4821. }
  4822. function getTickMarkLength(options) {
  4823. return options.drawTicks ? options.tickLength : 0;
  4824. }
  4825. function getTitleHeight(options, fallback) {
  4826. if (!options.display) {
  4827. return 0;
  4828. }
  4829. const font = toFont(options.font, fallback);
  4830. const padding = toPadding(options.padding);
  4831. const lines = isArray(options.text) ? options.text.length : 1;
  4832. return (lines * font.lineHeight) + padding.height;
  4833. }
  4834. function createScaleContext(parent, scale) {
  4835. return createContext(parent, {
  4836. scale,
  4837. type: 'scale'
  4838. });
  4839. }
  4840. function createTickContext(parent, index, tick) {
  4841. return createContext(parent, {
  4842. tick,
  4843. index,
  4844. type: 'tick'
  4845. });
  4846. }
  4847. function titleAlign(align, position, reverse) {
  4848. let ret = _toLeftRightCenter(align);
  4849. if ((reverse && position !== 'right') || (!reverse && position === 'right')) {
  4850. ret = reverseAlign(ret);
  4851. }
  4852. return ret;
  4853. }
  4854. function titleArgs(scale, offset, position, align) {
  4855. const {top, left, bottom, right, chart} = scale;
  4856. const {chartArea, scales} = chart;
  4857. let rotation = 0;
  4858. let maxWidth, titleX, titleY;
  4859. const height = bottom - top;
  4860. const width = right - left;
  4861. if (scale.isHorizontal()) {
  4862. titleX = _alignStartEnd(align, left, right);
  4863. if (isObject(position)) {
  4864. const positionAxisID = Object.keys(position)[0];
  4865. const value = position[positionAxisID];
  4866. titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
  4867. } else if (position === 'center') {
  4868. titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
  4869. } else {
  4870. titleY = offsetFromEdge(scale, position, offset);
  4871. }
  4872. maxWidth = right - left;
  4873. } else {
  4874. if (isObject(position)) {
  4875. const positionAxisID = Object.keys(position)[0];
  4876. const value = position[positionAxisID];
  4877. titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
  4878. } else if (position === 'center') {
  4879. titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
  4880. } else {
  4881. titleX = offsetFromEdge(scale, position, offset);
  4882. }
  4883. titleY = _alignStartEnd(align, bottom, top);
  4884. rotation = position === 'left' ? -HALF_PI : HALF_PI;
  4885. }
  4886. return {titleX, titleY, maxWidth, rotation};
  4887. }
  4888. class Scale extends Element {
  4889. constructor(cfg) {
  4890. super();
  4891. this.id = cfg.id;
  4892. this.type = cfg.type;
  4893. this.options = undefined;
  4894. this.ctx = cfg.ctx;
  4895. this.chart = cfg.chart;
  4896. this.top = undefined;
  4897. this.bottom = undefined;
  4898. this.left = undefined;
  4899. this.right = undefined;
  4900. this.width = undefined;
  4901. this.height = undefined;
  4902. this._margins = {
  4903. left: 0,
  4904. right: 0,
  4905. top: 0,
  4906. bottom: 0
  4907. };
  4908. this.maxWidth = undefined;
  4909. this.maxHeight = undefined;
  4910. this.paddingTop = undefined;
  4911. this.paddingBottom = undefined;
  4912. this.paddingLeft = undefined;
  4913. this.paddingRight = undefined;
  4914. this.axis = undefined;
  4915. this.labelRotation = undefined;
  4916. this.min = undefined;
  4917. this.max = undefined;
  4918. this._range = undefined;
  4919. this.ticks = [];
  4920. this._gridLineItems = null;
  4921. this._labelItems = null;
  4922. this._labelSizes = null;
  4923. this._length = 0;
  4924. this._maxLength = 0;
  4925. this._longestTextCache = {};
  4926. this._startPixel = undefined;
  4927. this._endPixel = undefined;
  4928. this._reversePixels = false;
  4929. this._userMax = undefined;
  4930. this._userMin = undefined;
  4931. this._suggestedMax = undefined;
  4932. this._suggestedMin = undefined;
  4933. this._ticksLength = 0;
  4934. this._borderValue = 0;
  4935. this._cache = {};
  4936. this._dataLimitsCached = false;
  4937. this.$context = undefined;
  4938. }
  4939. init(options) {
  4940. this.options = options.setContext(this.getContext());
  4941. this.axis = options.axis;
  4942. this._userMin = this.parse(options.min);
  4943. this._userMax = this.parse(options.max);
  4944. this._suggestedMin = this.parse(options.suggestedMin);
  4945. this._suggestedMax = this.parse(options.suggestedMax);
  4946. }
  4947. parse(raw, index) {
  4948. return raw;
  4949. }
  4950. getUserBounds() {
  4951. let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this;
  4952. _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
  4953. _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
  4954. _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
  4955. _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
  4956. return {
  4957. min: finiteOrDefault(_userMin, _suggestedMin),
  4958. max: finiteOrDefault(_userMax, _suggestedMax),
  4959. minDefined: isNumberFinite(_userMin),
  4960. maxDefined: isNumberFinite(_userMax)
  4961. };
  4962. }
  4963. getMinMax(canStack) {
  4964. let {min, max, minDefined, maxDefined} = this.getUserBounds();
  4965. let range;
  4966. if (minDefined && maxDefined) {
  4967. return {min, max};
  4968. }
  4969. const metas = this.getMatchingVisibleMetas();
  4970. for (let i = 0, ilen = metas.length; i < ilen; ++i) {
  4971. range = metas[i].controller.getMinMax(this, canStack);
  4972. if (!minDefined) {
  4973. min = Math.min(min, range.min);
  4974. }
  4975. if (!maxDefined) {
  4976. max = Math.max(max, range.max);
  4977. }
  4978. }
  4979. min = maxDefined && min > max ? max : min;
  4980. max = minDefined && min > max ? min : max;
  4981. return {
  4982. min: finiteOrDefault(min, finiteOrDefault(max, min)),
  4983. max: finiteOrDefault(max, finiteOrDefault(min, max))
  4984. };
  4985. }
  4986. getPadding() {
  4987. return {
  4988. left: this.paddingLeft || 0,
  4989. top: this.paddingTop || 0,
  4990. right: this.paddingRight || 0,
  4991. bottom: this.paddingBottom || 0
  4992. };
  4993. }
  4994. getTicks() {
  4995. return this.ticks;
  4996. }
  4997. getLabels() {
  4998. const data = this.chart.data;
  4999. return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
  5000. }
  5001. beforeLayout() {
  5002. this._cache = {};
  5003. this._dataLimitsCached = false;
  5004. }
  5005. beforeUpdate() {
  5006. callback(this.options.beforeUpdate, [this]);
  5007. }
  5008. update(maxWidth, maxHeight, margins) {
  5009. const {beginAtZero, grace, ticks: tickOpts} = this.options;
  5010. const sampleSize = tickOpts.sampleSize;
  5011. this.beforeUpdate();
  5012. this.maxWidth = maxWidth;
  5013. this.maxHeight = maxHeight;
  5014. this._margins = margins = Object.assign({
  5015. left: 0,
  5016. right: 0,
  5017. top: 0,
  5018. bottom: 0
  5019. }, margins);
  5020. this.ticks = null;
  5021. this._labelSizes = null;
  5022. this._gridLineItems = null;
  5023. this._labelItems = null;
  5024. this.beforeSetDimensions();
  5025. this.setDimensions();
  5026. this.afterSetDimensions();
  5027. this._maxLength = this.isHorizontal()
  5028. ? this.width + margins.left + margins.right
  5029. : this.height + margins.top + margins.bottom;
  5030. if (!this._dataLimitsCached) {
  5031. this.beforeDataLimits();
  5032. this.determineDataLimits();
  5033. this.afterDataLimits();
  5034. this._range = _addGrace(this, grace, beginAtZero);
  5035. this._dataLimitsCached = true;
  5036. }
  5037. this.beforeBuildTicks();
  5038. this.ticks = this.buildTicks() || [];
  5039. this.afterBuildTicks();
  5040. const samplingEnabled = sampleSize < this.ticks.length;
  5041. this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
  5042. this.configure();
  5043. this.beforeCalculateLabelRotation();
  5044. this.calculateLabelRotation();
  5045. this.afterCalculateLabelRotation();
  5046. if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
  5047. this.ticks = autoSkip(this, this.ticks);
  5048. this._labelSizes = null;
  5049. }
  5050. if (samplingEnabled) {
  5051. this._convertTicksToLabels(this.ticks);
  5052. }
  5053. this.beforeFit();
  5054. this.fit();
  5055. this.afterFit();
  5056. this.afterUpdate();
  5057. }
  5058. configure() {
  5059. let reversePixels = this.options.reverse;
  5060. let startPixel, endPixel;
  5061. if (this.isHorizontal()) {
  5062. startPixel = this.left;
  5063. endPixel = this.right;
  5064. } else {
  5065. startPixel = this.top;
  5066. endPixel = this.bottom;
  5067. reversePixels = !reversePixels;
  5068. }
  5069. this._startPixel = startPixel;
  5070. this._endPixel = endPixel;
  5071. this._reversePixels = reversePixels;
  5072. this._length = endPixel - startPixel;
  5073. this._alignToPixels = this.options.alignToPixels;
  5074. }
  5075. afterUpdate() {
  5076. callback(this.options.afterUpdate, [this]);
  5077. }
  5078. beforeSetDimensions() {
  5079. callback(this.options.beforeSetDimensions, [this]);
  5080. }
  5081. setDimensions() {
  5082. if (this.isHorizontal()) {
  5083. this.width = this.maxWidth;
  5084. this.left = 0;
  5085. this.right = this.width;
  5086. } else {
  5087. this.height = this.maxHeight;
  5088. this.top = 0;
  5089. this.bottom = this.height;
  5090. }
  5091. this.paddingLeft = 0;
  5092. this.paddingTop = 0;
  5093. this.paddingRight = 0;
  5094. this.paddingBottom = 0;
  5095. }
  5096. afterSetDimensions() {
  5097. callback(this.options.afterSetDimensions, [this]);
  5098. }
  5099. _callHooks(name) {
  5100. this.chart.notifyPlugins(name, this.getContext());
  5101. callback(this.options[name], [this]);
  5102. }
  5103. beforeDataLimits() {
  5104. this._callHooks('beforeDataLimits');
  5105. }
  5106. determineDataLimits() {}
  5107. afterDataLimits() {
  5108. this._callHooks('afterDataLimits');
  5109. }
  5110. beforeBuildTicks() {
  5111. this._callHooks('beforeBuildTicks');
  5112. }
  5113. buildTicks() {
  5114. return [];
  5115. }
  5116. afterBuildTicks() {
  5117. this._callHooks('afterBuildTicks');
  5118. }
  5119. beforeTickToLabelConversion() {
  5120. callback(this.options.beforeTickToLabelConversion, [this]);
  5121. }
  5122. generateTickLabels(ticks) {
  5123. const tickOpts = this.options.ticks;
  5124. let i, ilen, tick;
  5125. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  5126. tick = ticks[i];
  5127. tick.label = callback(tickOpts.callback, [tick.value, i, ticks], this);
  5128. }
  5129. }
  5130. afterTickToLabelConversion() {
  5131. callback(this.options.afterTickToLabelConversion, [this]);
  5132. }
  5133. beforeCalculateLabelRotation() {
  5134. callback(this.options.beforeCalculateLabelRotation, [this]);
  5135. }
  5136. calculateLabelRotation() {
  5137. const options = this.options;
  5138. const tickOpts = options.ticks;
  5139. const numTicks = this.ticks.length;
  5140. const minRotation = tickOpts.minRotation || 0;
  5141. const maxRotation = tickOpts.maxRotation;
  5142. let labelRotation = minRotation;
  5143. let tickWidth, maxHeight, maxLabelDiagonal;
  5144. if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
  5145. this.labelRotation = minRotation;
  5146. return;
  5147. }
  5148. const labelSizes = this._getLabelSizes();
  5149. const maxLabelWidth = labelSizes.widest.width;
  5150. const maxLabelHeight = labelSizes.highest.height;
  5151. const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
  5152. tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
  5153. if (maxLabelWidth + 6 > tickWidth) {
  5154. tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
  5155. maxHeight = this.maxHeight - getTickMarkLength(options.grid)
  5156. - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
  5157. maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
  5158. labelRotation = toDegrees(Math.min(
  5159. Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)),
  5160. Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))
  5161. ));
  5162. labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
  5163. }
  5164. this.labelRotation = labelRotation;
  5165. }
  5166. afterCalculateLabelRotation() {
  5167. callback(this.options.afterCalculateLabelRotation, [this]);
  5168. }
  5169. beforeFit() {
  5170. callback(this.options.beforeFit, [this]);
  5171. }
  5172. fit() {
  5173. const minSize = {
  5174. width: 0,
  5175. height: 0
  5176. };
  5177. const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = this;
  5178. const display = this._isVisible();
  5179. const isHorizontal = this.isHorizontal();
  5180. if (display) {
  5181. const titleHeight = getTitleHeight(titleOpts, chart.options.font);
  5182. if (isHorizontal) {
  5183. minSize.width = this.maxWidth;
  5184. minSize.height = getTickMarkLength(gridOpts) + titleHeight;
  5185. } else {
  5186. minSize.height = this.maxHeight;
  5187. minSize.width = getTickMarkLength(gridOpts) + titleHeight;
  5188. }
  5189. if (tickOpts.display && this.ticks.length) {
  5190. const {first, last, widest, highest} = this._getLabelSizes();
  5191. const tickPadding = tickOpts.padding * 2;
  5192. const angleRadians = toRadians(this.labelRotation);
  5193. const cos = Math.cos(angleRadians);
  5194. const sin = Math.sin(angleRadians);
  5195. if (isHorizontal) {
  5196. const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
  5197. minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
  5198. } else {
  5199. const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
  5200. minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
  5201. }
  5202. this._calculatePadding(first, last, sin, cos);
  5203. }
  5204. }
  5205. this._handleMargins();
  5206. if (isHorizontal) {
  5207. this.width = this._length = chart.width - this._margins.left - this._margins.right;
  5208. this.height = minSize.height;
  5209. } else {
  5210. this.width = minSize.width;
  5211. this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
  5212. }
  5213. }
  5214. _calculatePadding(first, last, sin, cos) {
  5215. const {ticks: {align, padding}, position} = this.options;
  5216. const isRotated = this.labelRotation !== 0;
  5217. const labelsBelowTicks = position !== 'top' && this.axis === 'x';
  5218. if (this.isHorizontal()) {
  5219. const offsetLeft = this.getPixelForTick(0) - this.left;
  5220. const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
  5221. let paddingLeft = 0;
  5222. let paddingRight = 0;
  5223. if (isRotated) {
  5224. if (labelsBelowTicks) {
  5225. paddingLeft = cos * first.width;
  5226. paddingRight = sin * last.height;
  5227. } else {
  5228. paddingLeft = sin * first.height;
  5229. paddingRight = cos * last.width;
  5230. }
  5231. } else if (align === 'start') {
  5232. paddingRight = last.width;
  5233. } else if (align === 'end') {
  5234. paddingLeft = first.width;
  5235. } else if (align !== 'inner') {
  5236. paddingLeft = first.width / 2;
  5237. paddingRight = last.width / 2;
  5238. }
  5239. this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
  5240. this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
  5241. } else {
  5242. let paddingTop = last.height / 2;
  5243. let paddingBottom = first.height / 2;
  5244. if (align === 'start') {
  5245. paddingTop = 0;
  5246. paddingBottom = first.height;
  5247. } else if (align === 'end') {
  5248. paddingTop = last.height;
  5249. paddingBottom = 0;
  5250. }
  5251. this.paddingTop = paddingTop + padding;
  5252. this.paddingBottom = paddingBottom + padding;
  5253. }
  5254. }
  5255. _handleMargins() {
  5256. if (this._margins) {
  5257. this._margins.left = Math.max(this.paddingLeft, this._margins.left);
  5258. this._margins.top = Math.max(this.paddingTop, this._margins.top);
  5259. this._margins.right = Math.max(this.paddingRight, this._margins.right);
  5260. this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
  5261. }
  5262. }
  5263. afterFit() {
  5264. callback(this.options.afterFit, [this]);
  5265. }
  5266. isHorizontal() {
  5267. const {axis, position} = this.options;
  5268. return position === 'top' || position === 'bottom' || axis === 'x';
  5269. }
  5270. isFullSize() {
  5271. return this.options.fullSize;
  5272. }
  5273. _convertTicksToLabels(ticks) {
  5274. this.beforeTickToLabelConversion();
  5275. this.generateTickLabels(ticks);
  5276. let i, ilen;
  5277. for (i = 0, ilen = ticks.length; i < ilen; i++) {
  5278. if (isNullOrUndef(ticks[i].label)) {
  5279. ticks.splice(i, 1);
  5280. ilen--;
  5281. i--;
  5282. }
  5283. }
  5284. this.afterTickToLabelConversion();
  5285. }
  5286. _getLabelSizes() {
  5287. let labelSizes = this._labelSizes;
  5288. if (!labelSizes) {
  5289. const sampleSize = this.options.ticks.sampleSize;
  5290. let ticks = this.ticks;
  5291. if (sampleSize < ticks.length) {
  5292. ticks = sample(ticks, sampleSize);
  5293. }
  5294. this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length);
  5295. }
  5296. return labelSizes;
  5297. }
  5298. _computeLabelSizes(ticks, length) {
  5299. const {ctx, _longestTextCache: caches} = this;
  5300. const widths = [];
  5301. const heights = [];
  5302. let widestLabelSize = 0;
  5303. let highestLabelSize = 0;
  5304. let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
  5305. for (i = 0; i < length; ++i) {
  5306. label = ticks[i].label;
  5307. tickFont = this._resolveTickFontOptions(i);
  5308. ctx.font = fontString = tickFont.string;
  5309. cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
  5310. lineHeight = tickFont.lineHeight;
  5311. width = height = 0;
  5312. if (!isNullOrUndef(label) && !isArray(label)) {
  5313. width = _measureText(ctx, cache.data, cache.gc, width, label);
  5314. height = lineHeight;
  5315. } else if (isArray(label)) {
  5316. for (j = 0, jlen = label.length; j < jlen; ++j) {
  5317. nestedLabel = label[j];
  5318. if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
  5319. width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  5320. height += lineHeight;
  5321. }
  5322. }
  5323. }
  5324. widths.push(width);
  5325. heights.push(height);
  5326. widestLabelSize = Math.max(width, widestLabelSize);
  5327. highestLabelSize = Math.max(height, highestLabelSize);
  5328. }
  5329. garbageCollect(caches, length);
  5330. const widest = widths.indexOf(widestLabelSize);
  5331. const highest = heights.indexOf(highestLabelSize);
  5332. const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});
  5333. return {
  5334. first: valueAt(0),
  5335. last: valueAt(length - 1),
  5336. widest: valueAt(widest),
  5337. highest: valueAt(highest),
  5338. widths,
  5339. heights,
  5340. };
  5341. }
  5342. getLabelForValue(value) {
  5343. return value;
  5344. }
  5345. getPixelForValue(value, index) {
  5346. return NaN;
  5347. }
  5348. getValueForPixel(pixel) {}
  5349. getPixelForTick(index) {
  5350. const ticks = this.ticks;
  5351. if (index < 0 || index > ticks.length - 1) {
  5352. return null;
  5353. }
  5354. return this.getPixelForValue(ticks[index].value);
  5355. }
  5356. getPixelForDecimal(decimal) {
  5357. if (this._reversePixels) {
  5358. decimal = 1 - decimal;
  5359. }
  5360. const pixel = this._startPixel + decimal * this._length;
  5361. return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
  5362. }
  5363. getDecimalForPixel(pixel) {
  5364. const decimal = (pixel - this._startPixel) / this._length;
  5365. return this._reversePixels ? 1 - decimal : decimal;
  5366. }
  5367. getBasePixel() {
  5368. return this.getPixelForValue(this.getBaseValue());
  5369. }
  5370. getBaseValue() {
  5371. const {min, max} = this;
  5372. return min < 0 && max < 0 ? max :
  5373. min > 0 && max > 0 ? min :
  5374. 0;
  5375. }
  5376. getContext(index) {
  5377. const ticks = this.ticks || [];
  5378. if (index >= 0 && index < ticks.length) {
  5379. const tick = ticks[index];
  5380. return tick.$context ||
  5381. (tick.$context = createTickContext(this.getContext(), index, tick));
  5382. }
  5383. return this.$context ||
  5384. (this.$context = createScaleContext(this.chart.getContext(), this));
  5385. }
  5386. _tickSize() {
  5387. const optionTicks = this.options.ticks;
  5388. const rot = toRadians(this.labelRotation);
  5389. const cos = Math.abs(Math.cos(rot));
  5390. const sin = Math.abs(Math.sin(rot));
  5391. const labelSizes = this._getLabelSizes();
  5392. const padding = optionTicks.autoSkipPadding || 0;
  5393. const w = labelSizes ? labelSizes.widest.width + padding : 0;
  5394. const h = labelSizes ? labelSizes.highest.height + padding : 0;
  5395. return this.isHorizontal()
  5396. ? h * cos > w * sin ? w / cos : h / sin
  5397. : h * sin < w * cos ? h / cos : w / sin;
  5398. }
  5399. _isVisible() {
  5400. const display = this.options.display;
  5401. if (display !== 'auto') {
  5402. return !!display;
  5403. }
  5404. return this.getMatchingVisibleMetas().length > 0;
  5405. }
  5406. _computeGridLineItems(chartArea) {
  5407. const axis = this.axis;
  5408. const chart = this.chart;
  5409. const options = this.options;
  5410. const {grid, position} = options;
  5411. const offset = grid.offset;
  5412. const isHorizontal = this.isHorizontal();
  5413. const ticks = this.ticks;
  5414. const ticksLength = ticks.length + (offset ? 1 : 0);
  5415. const tl = getTickMarkLength(grid);
  5416. const items = [];
  5417. const borderOpts = grid.setContext(this.getContext());
  5418. const axisWidth = borderOpts.drawBorder ? borderOpts.borderWidth : 0;
  5419. const axisHalfWidth = axisWidth / 2;
  5420. const alignBorderValue = function(pixel) {
  5421. return _alignPixel(chart, pixel, axisWidth);
  5422. };
  5423. let borderValue, i, lineValue, alignedLineValue;
  5424. let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
  5425. if (position === 'top') {
  5426. borderValue = alignBorderValue(this.bottom);
  5427. ty1 = this.bottom - tl;
  5428. ty2 = borderValue - axisHalfWidth;
  5429. y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
  5430. y2 = chartArea.bottom;
  5431. } else if (position === 'bottom') {
  5432. borderValue = alignBorderValue(this.top);
  5433. y1 = chartArea.top;
  5434. y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
  5435. ty1 = borderValue + axisHalfWidth;
  5436. ty2 = this.top + tl;
  5437. } else if (position === 'left') {
  5438. borderValue = alignBorderValue(this.right);
  5439. tx1 = this.right - tl;
  5440. tx2 = borderValue - axisHalfWidth;
  5441. x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
  5442. x2 = chartArea.right;
  5443. } else if (position === 'right') {
  5444. borderValue = alignBorderValue(this.left);
  5445. x1 = chartArea.left;
  5446. x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
  5447. tx1 = borderValue + axisHalfWidth;
  5448. tx2 = this.left + tl;
  5449. } else if (axis === 'x') {
  5450. if (position === 'center') {
  5451. borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
  5452. } else if (isObject(position)) {
  5453. const positionAxisID = Object.keys(position)[0];
  5454. const value = position[positionAxisID];
  5455. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  5456. }
  5457. y1 = chartArea.top;
  5458. y2 = chartArea.bottom;
  5459. ty1 = borderValue + axisHalfWidth;
  5460. ty2 = ty1 + tl;
  5461. } else if (axis === 'y') {
  5462. if (position === 'center') {
  5463. borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
  5464. } else if (isObject(position)) {
  5465. const positionAxisID = Object.keys(position)[0];
  5466. const value = position[positionAxisID];
  5467. borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
  5468. }
  5469. tx1 = borderValue - axisHalfWidth;
  5470. tx2 = tx1 - tl;
  5471. x1 = chartArea.left;
  5472. x2 = chartArea.right;
  5473. }
  5474. const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
  5475. const step = Math.max(1, Math.ceil(ticksLength / limit));
  5476. for (i = 0; i < ticksLength; i += step) {
  5477. const optsAtIndex = grid.setContext(this.getContext(i));
  5478. const lineWidth = optsAtIndex.lineWidth;
  5479. const lineColor = optsAtIndex.color;
  5480. const borderDash = grid.borderDash || [];
  5481. const borderDashOffset = optsAtIndex.borderDashOffset;
  5482. const tickWidth = optsAtIndex.tickWidth;
  5483. const tickColor = optsAtIndex.tickColor;
  5484. const tickBorderDash = optsAtIndex.tickBorderDash || [];
  5485. const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
  5486. lineValue = getPixelForGridLine(this, i, offset);
  5487. if (lineValue === undefined) {
  5488. continue;
  5489. }
  5490. alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
  5491. if (isHorizontal) {
  5492. tx1 = tx2 = x1 = x2 = alignedLineValue;
  5493. } else {
  5494. ty1 = ty2 = y1 = y2 = alignedLineValue;
  5495. }
  5496. items.push({
  5497. tx1,
  5498. ty1,
  5499. tx2,
  5500. ty2,
  5501. x1,
  5502. y1,
  5503. x2,
  5504. y2,
  5505. width: lineWidth,
  5506. color: lineColor,
  5507. borderDash,
  5508. borderDashOffset,
  5509. tickWidth,
  5510. tickColor,
  5511. tickBorderDash,
  5512. tickBorderDashOffset,
  5513. });
  5514. }
  5515. this._ticksLength = ticksLength;
  5516. this._borderValue = borderValue;
  5517. return items;
  5518. }
  5519. _computeLabelItems(chartArea) {
  5520. const axis = this.axis;
  5521. const options = this.options;
  5522. const {position, ticks: optionTicks} = options;
  5523. const isHorizontal = this.isHorizontal();
  5524. const ticks = this.ticks;
  5525. const {align, crossAlign, padding, mirror} = optionTicks;
  5526. const tl = getTickMarkLength(options.grid);
  5527. const tickAndPadding = tl + padding;
  5528. const hTickAndPadding = mirror ? -padding : tickAndPadding;
  5529. const rotation = -toRadians(this.labelRotation);
  5530. const items = [];
  5531. let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
  5532. let textBaseline = 'middle';
  5533. if (position === 'top') {
  5534. y = this.bottom - hTickAndPadding;
  5535. textAlign = this._getXAxisLabelAlignment();
  5536. } else if (position === 'bottom') {
  5537. y = this.top + hTickAndPadding;
  5538. textAlign = this._getXAxisLabelAlignment();
  5539. } else if (position === 'left') {
  5540. const ret = this._getYAxisLabelAlignment(tl);
  5541. textAlign = ret.textAlign;
  5542. x = ret.x;
  5543. } else if (position === 'right') {
  5544. const ret = this._getYAxisLabelAlignment(tl);
  5545. textAlign = ret.textAlign;
  5546. x = ret.x;
  5547. } else if (axis === 'x') {
  5548. if (position === 'center') {
  5549. y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;
  5550. } else if (isObject(position)) {
  5551. const positionAxisID = Object.keys(position)[0];
  5552. const value = position[positionAxisID];
  5553. y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
  5554. }
  5555. textAlign = this._getXAxisLabelAlignment();
  5556. } else if (axis === 'y') {
  5557. if (position === 'center') {
  5558. x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;
  5559. } else if (isObject(position)) {
  5560. const positionAxisID = Object.keys(position)[0];
  5561. const value = position[positionAxisID];
  5562. x = this.chart.scales[positionAxisID].getPixelForValue(value);
  5563. }
  5564. textAlign = this._getYAxisLabelAlignment(tl).textAlign;
  5565. }
  5566. if (axis === 'y') {
  5567. if (align === 'start') {
  5568. textBaseline = 'top';
  5569. } else if (align === 'end') {
  5570. textBaseline = 'bottom';
  5571. }
  5572. }
  5573. const labelSizes = this._getLabelSizes();
  5574. for (i = 0, ilen = ticks.length; i < ilen; ++i) {
  5575. tick = ticks[i];
  5576. label = tick.label;
  5577. const optsAtIndex = optionTicks.setContext(this.getContext(i));
  5578. pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
  5579. font = this._resolveTickFontOptions(i);
  5580. lineHeight = font.lineHeight;
  5581. lineCount = isArray(label) ? label.length : 1;
  5582. const halfCount = lineCount / 2;
  5583. const color = optsAtIndex.color;
  5584. const strokeColor = optsAtIndex.textStrokeColor;
  5585. const strokeWidth = optsAtIndex.textStrokeWidth;
  5586. let tickTextAlign = textAlign;
  5587. if (isHorizontal) {
  5588. x = pixel;
  5589. if (textAlign === 'inner') {
  5590. if (i === ilen - 1) {
  5591. tickTextAlign = !this.options.reverse ? 'right' : 'left';
  5592. } else if (i === 0) {
  5593. tickTextAlign = !this.options.reverse ? 'left' : 'right';
  5594. } else {
  5595. tickTextAlign = 'center';
  5596. }
  5597. }
  5598. if (position === 'top') {
  5599. if (crossAlign === 'near' || rotation !== 0) {
  5600. textOffset = -lineCount * lineHeight + lineHeight / 2;
  5601. } else if (crossAlign === 'center') {
  5602. textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
  5603. } else {
  5604. textOffset = -labelSizes.highest.height + lineHeight / 2;
  5605. }
  5606. } else {
  5607. if (crossAlign === 'near' || rotation !== 0) {
  5608. textOffset = lineHeight / 2;
  5609. } else if (crossAlign === 'center') {
  5610. textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
  5611. } else {
  5612. textOffset = labelSizes.highest.height - lineCount * lineHeight;
  5613. }
  5614. }
  5615. if (mirror) {
  5616. textOffset *= -1;
  5617. }
  5618. } else {
  5619. y = pixel;
  5620. textOffset = (1 - lineCount) * lineHeight / 2;
  5621. }
  5622. let backdrop;
  5623. if (optsAtIndex.showLabelBackdrop) {
  5624. const labelPadding = toPadding(optsAtIndex.backdropPadding);
  5625. const height = labelSizes.heights[i];
  5626. const width = labelSizes.widths[i];
  5627. let top = y + textOffset - labelPadding.top;
  5628. let left = x - labelPadding.left;
  5629. switch (textBaseline) {
  5630. case 'middle':
  5631. top -= height / 2;
  5632. break;
  5633. case 'bottom':
  5634. top -= height;
  5635. break;
  5636. }
  5637. switch (textAlign) {
  5638. case 'center':
  5639. left -= width / 2;
  5640. break;
  5641. case 'right':
  5642. left -= width;
  5643. break;
  5644. }
  5645. backdrop = {
  5646. left,
  5647. top,
  5648. width: width + labelPadding.width,
  5649. height: height + labelPadding.height,
  5650. color: optsAtIndex.backdropColor,
  5651. };
  5652. }
  5653. items.push({
  5654. rotation,
  5655. label,
  5656. font,
  5657. color,
  5658. strokeColor,
  5659. strokeWidth,
  5660. textOffset,
  5661. textAlign: tickTextAlign,
  5662. textBaseline,
  5663. translation: [x, y],
  5664. backdrop,
  5665. });
  5666. }
  5667. return items;
  5668. }
  5669. _getXAxisLabelAlignment() {
  5670. const {position, ticks} = this.options;
  5671. const rotation = -toRadians(this.labelRotation);
  5672. if (rotation) {
  5673. return position === 'top' ? 'left' : 'right';
  5674. }
  5675. let align = 'center';
  5676. if (ticks.align === 'start') {
  5677. align = 'left';
  5678. } else if (ticks.align === 'end') {
  5679. align = 'right';
  5680. } else if (ticks.align === 'inner') {
  5681. align = 'inner';
  5682. }
  5683. return align;
  5684. }
  5685. _getYAxisLabelAlignment(tl) {
  5686. const {position, ticks: {crossAlign, mirror, padding}} = this.options;
  5687. const labelSizes = this._getLabelSizes();
  5688. const tickAndPadding = tl + padding;
  5689. const widest = labelSizes.widest.width;
  5690. let textAlign;
  5691. let x;
  5692. if (position === 'left') {
  5693. if (mirror) {
  5694. x = this.right + padding;
  5695. if (crossAlign === 'near') {
  5696. textAlign = 'left';
  5697. } else if (crossAlign === 'center') {
  5698. textAlign = 'center';
  5699. x += (widest / 2);
  5700. } else {
  5701. textAlign = 'right';
  5702. x += widest;
  5703. }
  5704. } else {
  5705. x = this.right - tickAndPadding;
  5706. if (crossAlign === 'near') {
  5707. textAlign = 'right';
  5708. } else if (crossAlign === 'center') {
  5709. textAlign = 'center';
  5710. x -= (widest / 2);
  5711. } else {
  5712. textAlign = 'left';
  5713. x = this.left;
  5714. }
  5715. }
  5716. } else if (position === 'right') {
  5717. if (mirror) {
  5718. x = this.left + padding;
  5719. if (crossAlign === 'near') {
  5720. textAlign = 'right';
  5721. } else if (crossAlign === 'center') {
  5722. textAlign = 'center';
  5723. x -= (widest / 2);
  5724. } else {
  5725. textAlign = 'left';
  5726. x -= widest;
  5727. }
  5728. } else {
  5729. x = this.left + tickAndPadding;
  5730. if (crossAlign === 'near') {
  5731. textAlign = 'left';
  5732. } else if (crossAlign === 'center') {
  5733. textAlign = 'center';
  5734. x += widest / 2;
  5735. } else {
  5736. textAlign = 'right';
  5737. x = this.right;
  5738. }
  5739. }
  5740. } else {
  5741. textAlign = 'right';
  5742. }
  5743. return {textAlign, x};
  5744. }
  5745. _computeLabelArea() {
  5746. if (this.options.ticks.mirror) {
  5747. return;
  5748. }
  5749. const chart = this.chart;
  5750. const position = this.options.position;
  5751. if (position === 'left' || position === 'right') {
  5752. return {top: 0, left: this.left, bottom: chart.height, right: this.right};
  5753. } if (position === 'top' || position === 'bottom') {
  5754. return {top: this.top, left: 0, bottom: this.bottom, right: chart.width};
  5755. }
  5756. }
  5757. drawBackground() {
  5758. const {ctx, options: {backgroundColor}, left, top, width, height} = this;
  5759. if (backgroundColor) {
  5760. ctx.save();
  5761. ctx.fillStyle = backgroundColor;
  5762. ctx.fillRect(left, top, width, height);
  5763. ctx.restore();
  5764. }
  5765. }
  5766. getLineWidthForValue(value) {
  5767. const grid = this.options.grid;
  5768. if (!this._isVisible() || !grid.display) {
  5769. return 0;
  5770. }
  5771. const ticks = this.ticks;
  5772. const index = ticks.findIndex(t => t.value === value);
  5773. if (index >= 0) {
  5774. const opts = grid.setContext(this.getContext(index));
  5775. return opts.lineWidth;
  5776. }
  5777. return 0;
  5778. }
  5779. drawGrid(chartArea) {
  5780. const grid = this.options.grid;
  5781. const ctx = this.ctx;
  5782. const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
  5783. let i, ilen;
  5784. const drawLine = (p1, p2, style) => {
  5785. if (!style.width || !style.color) {
  5786. return;
  5787. }
  5788. ctx.save();
  5789. ctx.lineWidth = style.width;
  5790. ctx.strokeStyle = style.color;
  5791. ctx.setLineDash(style.borderDash || []);
  5792. ctx.lineDashOffset = style.borderDashOffset;
  5793. ctx.beginPath();
  5794. ctx.moveTo(p1.x, p1.y);
  5795. ctx.lineTo(p2.x, p2.y);
  5796. ctx.stroke();
  5797. ctx.restore();
  5798. };
  5799. if (grid.display) {
  5800. for (i = 0, ilen = items.length; i < ilen; ++i) {
  5801. const item = items[i];
  5802. if (grid.drawOnChartArea) {
  5803. drawLine(
  5804. {x: item.x1, y: item.y1},
  5805. {x: item.x2, y: item.y2},
  5806. item
  5807. );
  5808. }
  5809. if (grid.drawTicks) {
  5810. drawLine(
  5811. {x: item.tx1, y: item.ty1},
  5812. {x: item.tx2, y: item.ty2},
  5813. {
  5814. color: item.tickColor,
  5815. width: item.tickWidth,
  5816. borderDash: item.tickBorderDash,
  5817. borderDashOffset: item.tickBorderDashOffset
  5818. }
  5819. );
  5820. }
  5821. }
  5822. }
  5823. }
  5824. drawBorder() {
  5825. const {chart, ctx, options: {grid}} = this;
  5826. const borderOpts = grid.setContext(this.getContext());
  5827. const axisWidth = grid.drawBorder ? borderOpts.borderWidth : 0;
  5828. if (!axisWidth) {
  5829. return;
  5830. }
  5831. const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
  5832. const borderValue = this._borderValue;
  5833. let x1, x2, y1, y2;
  5834. if (this.isHorizontal()) {
  5835. x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
  5836. x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
  5837. y1 = y2 = borderValue;
  5838. } else {
  5839. y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
  5840. y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
  5841. x1 = x2 = borderValue;
  5842. }
  5843. ctx.save();
  5844. ctx.lineWidth = borderOpts.borderWidth;
  5845. ctx.strokeStyle = borderOpts.borderColor;
  5846. ctx.beginPath();
  5847. ctx.moveTo(x1, y1);
  5848. ctx.lineTo(x2, y2);
  5849. ctx.stroke();
  5850. ctx.restore();
  5851. }
  5852. drawLabels(chartArea) {
  5853. const optionTicks = this.options.ticks;
  5854. if (!optionTicks.display) {
  5855. return;
  5856. }
  5857. const ctx = this.ctx;
  5858. const area = this._computeLabelArea();
  5859. if (area) {
  5860. clipArea(ctx, area);
  5861. }
  5862. const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
  5863. let i, ilen;
  5864. for (i = 0, ilen = items.length; i < ilen; ++i) {
  5865. const item = items[i];
  5866. const tickFont = item.font;
  5867. const label = item.label;
  5868. if (item.backdrop) {
  5869. ctx.fillStyle = item.backdrop.color;
  5870. ctx.fillRect(item.backdrop.left, item.backdrop.top, item.backdrop.width, item.backdrop.height);
  5871. }
  5872. let y = item.textOffset;
  5873. renderText(ctx, label, 0, y, tickFont, item);
  5874. }
  5875. if (area) {
  5876. unclipArea(ctx);
  5877. }
  5878. }
  5879. drawTitle() {
  5880. const {ctx, options: {position, title, reverse}} = this;
  5881. if (!title.display) {
  5882. return;
  5883. }
  5884. const font = toFont(title.font);
  5885. const padding = toPadding(title.padding);
  5886. const align = title.align;
  5887. let offset = font.lineHeight / 2;
  5888. if (position === 'bottom' || position === 'center' || isObject(position)) {
  5889. offset += padding.bottom;
  5890. if (isArray(title.text)) {
  5891. offset += font.lineHeight * (title.text.length - 1);
  5892. }
  5893. } else {
  5894. offset += padding.top;
  5895. }
  5896. const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);
  5897. renderText(ctx, title.text, 0, 0, font, {
  5898. color: title.color,
  5899. maxWidth,
  5900. rotation,
  5901. textAlign: titleAlign(align, position, reverse),
  5902. textBaseline: 'middle',
  5903. translation: [titleX, titleY],
  5904. });
  5905. }
  5906. draw(chartArea) {
  5907. if (!this._isVisible()) {
  5908. return;
  5909. }
  5910. this.drawBackground();
  5911. this.drawGrid(chartArea);
  5912. this.drawBorder();
  5913. this.drawTitle();
  5914. this.drawLabels(chartArea);
  5915. }
  5916. _layers() {
  5917. const opts = this.options;
  5918. const tz = opts.ticks && opts.ticks.z || 0;
  5919. const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
  5920. if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
  5921. return [{
  5922. z: tz,
  5923. draw: (chartArea) => {
  5924. this.draw(chartArea);
  5925. }
  5926. }];
  5927. }
  5928. return [{
  5929. z: gz,
  5930. draw: (chartArea) => {
  5931. this.drawBackground();
  5932. this.drawGrid(chartArea);
  5933. this.drawTitle();
  5934. }
  5935. }, {
  5936. z: gz + 1,
  5937. draw: () => {
  5938. this.drawBorder();
  5939. }
  5940. }, {
  5941. z: tz,
  5942. draw: (chartArea) => {
  5943. this.drawLabels(chartArea);
  5944. }
  5945. }];
  5946. }
  5947. getMatchingVisibleMetas(type) {
  5948. const metas = this.chart.getSortedVisibleDatasetMetas();
  5949. const axisID = this.axis + 'AxisID';
  5950. const result = [];
  5951. let i, ilen;
  5952. for (i = 0, ilen = metas.length; i < ilen; ++i) {
  5953. const meta = metas[i];
  5954. if (meta[axisID] === this.id && (!type || meta.type === type)) {
  5955. result.push(meta);
  5956. }
  5957. }
  5958. return result;
  5959. }
  5960. _resolveTickFontOptions(index) {
  5961. const opts = this.options.ticks.setContext(this.getContext(index));
  5962. return toFont(opts.font);
  5963. }
  5964. _maxDigits() {
  5965. const fontSize = this._resolveTickFontOptions(0).lineHeight;
  5966. return (this.isHorizontal() ? this.width : this.height) / fontSize;
  5967. }
  5968. }
  5969. class TypedRegistry {
  5970. constructor(type, scope, override) {
  5971. this.type = type;
  5972. this.scope = scope;
  5973. this.override = override;
  5974. this.items = Object.create(null);
  5975. }
  5976. isForType(type) {
  5977. return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
  5978. }
  5979. register(item) {
  5980. const proto = Object.getPrototypeOf(item);
  5981. let parentScope;
  5982. if (isIChartComponent(proto)) {
  5983. parentScope = this.register(proto);
  5984. }
  5985. const items = this.items;
  5986. const id = item.id;
  5987. const scope = this.scope + '.' + id;
  5988. if (!id) {
  5989. throw new Error('class does not have id: ' + item);
  5990. }
  5991. if (id in items) {
  5992. return scope;
  5993. }
  5994. items[id] = item;
  5995. registerDefaults(item, scope, parentScope);
  5996. if (this.override) {
  5997. defaults.override(item.id, item.overrides);
  5998. }
  5999. return scope;
  6000. }
  6001. get(id) {
  6002. return this.items[id];
  6003. }
  6004. unregister(item) {
  6005. const items = this.items;
  6006. const id = item.id;
  6007. const scope = this.scope;
  6008. if (id in items) {
  6009. delete items[id];
  6010. }
  6011. if (scope && id in defaults[scope]) {
  6012. delete defaults[scope][id];
  6013. if (this.override) {
  6014. delete overrides[id];
  6015. }
  6016. }
  6017. }
  6018. }
  6019. function registerDefaults(item, scope, parentScope) {
  6020. const itemDefaults = merge(Object.create(null), [
  6021. parentScope ? defaults.get(parentScope) : {},
  6022. defaults.get(scope),
  6023. item.defaults
  6024. ]);
  6025. defaults.set(scope, itemDefaults);
  6026. if (item.defaultRoutes) {
  6027. routeDefaults(scope, item.defaultRoutes);
  6028. }
  6029. if (item.descriptors) {
  6030. defaults.describe(scope, item.descriptors);
  6031. }
  6032. }
  6033. function routeDefaults(scope, routes) {
  6034. Object.keys(routes).forEach(property => {
  6035. const propertyParts = property.split('.');
  6036. const sourceName = propertyParts.pop();
  6037. const sourceScope = [scope].concat(propertyParts).join('.');
  6038. const parts = routes[property].split('.');
  6039. const targetName = parts.pop();
  6040. const targetScope = parts.join('.');
  6041. defaults.route(sourceScope, sourceName, targetScope, targetName);
  6042. });
  6043. }
  6044. function isIChartComponent(proto) {
  6045. return 'id' in proto && 'defaults' in proto;
  6046. }
  6047. class Registry {
  6048. constructor() {
  6049. this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
  6050. this.elements = new TypedRegistry(Element, 'elements');
  6051. this.plugins = new TypedRegistry(Object, 'plugins');
  6052. this.scales = new TypedRegistry(Scale, 'scales');
  6053. this._typedRegistries = [this.controllers, this.scales, this.elements];
  6054. }
  6055. add(...args) {
  6056. this._each('register', args);
  6057. }
  6058. remove(...args) {
  6059. this._each('unregister', args);
  6060. }
  6061. addControllers(...args) {
  6062. this._each('register', args, this.controllers);
  6063. }
  6064. addElements(...args) {
  6065. this._each('register', args, this.elements);
  6066. }
  6067. addPlugins(...args) {
  6068. this._each('register', args, this.plugins);
  6069. }
  6070. addScales(...args) {
  6071. this._each('register', args, this.scales);
  6072. }
  6073. getController(id) {
  6074. return this._get(id, this.controllers, 'controller');
  6075. }
  6076. getElement(id) {
  6077. return this._get(id, this.elements, 'element');
  6078. }
  6079. getPlugin(id) {
  6080. return this._get(id, this.plugins, 'plugin');
  6081. }
  6082. getScale(id) {
  6083. return this._get(id, this.scales, 'scale');
  6084. }
  6085. removeControllers(...args) {
  6086. this._each('unregister', args, this.controllers);
  6087. }
  6088. removeElements(...args) {
  6089. this._each('unregister', args, this.elements);
  6090. }
  6091. removePlugins(...args) {
  6092. this._each('unregister', args, this.plugins);
  6093. }
  6094. removeScales(...args) {
  6095. this._each('unregister', args, this.scales);
  6096. }
  6097. _each(method, args, typedRegistry) {
  6098. [...args].forEach(arg => {
  6099. const reg = typedRegistry || this._getRegistryForType(arg);
  6100. if (typedRegistry || reg.isForType(arg) || (reg === this.plugins && arg.id)) {
  6101. this._exec(method, reg, arg);
  6102. } else {
  6103. each(arg, item => {
  6104. const itemReg = typedRegistry || this._getRegistryForType(item);
  6105. this._exec(method, itemReg, item);
  6106. });
  6107. }
  6108. });
  6109. }
  6110. _exec(method, registry, component) {
  6111. const camelMethod = _capitalize(method);
  6112. callback(component['before' + camelMethod], [], component);
  6113. registry[method](component);
  6114. callback(component['after' + camelMethod], [], component);
  6115. }
  6116. _getRegistryForType(type) {
  6117. for (let i = 0; i < this._typedRegistries.length; i++) {
  6118. const reg = this._typedRegistries[i];
  6119. if (reg.isForType(type)) {
  6120. return reg;
  6121. }
  6122. }
  6123. return this.plugins;
  6124. }
  6125. _get(id, typedRegistry, type) {
  6126. const item = typedRegistry.get(id);
  6127. if (item === undefined) {
  6128. throw new Error('"' + id + '" is not a registered ' + type + '.');
  6129. }
  6130. return item;
  6131. }
  6132. }
  6133. var registry = new Registry();
  6134. class PluginService {
  6135. constructor() {
  6136. this._init = [];
  6137. }
  6138. notify(chart, hook, args, filter) {
  6139. if (hook === 'beforeInit') {
  6140. this._init = this._createDescriptors(chart, true);
  6141. this._notify(this._init, chart, 'install');
  6142. }
  6143. const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
  6144. const result = this._notify(descriptors, chart, hook, args);
  6145. if (hook === 'afterDestroy') {
  6146. this._notify(descriptors, chart, 'stop');
  6147. this._notify(this._init, chart, 'uninstall');
  6148. }
  6149. return result;
  6150. }
  6151. _notify(descriptors, chart, hook, args) {
  6152. args = args || {};
  6153. for (const descriptor of descriptors) {
  6154. const plugin = descriptor.plugin;
  6155. const method = plugin[hook];
  6156. const params = [chart, args, descriptor.options];
  6157. if (callback(method, params, plugin) === false && args.cancelable) {
  6158. return false;
  6159. }
  6160. }
  6161. return true;
  6162. }
  6163. invalidate() {
  6164. if (!isNullOrUndef(this._cache)) {
  6165. this._oldCache = this._cache;
  6166. this._cache = undefined;
  6167. }
  6168. }
  6169. _descriptors(chart) {
  6170. if (this._cache) {
  6171. return this._cache;
  6172. }
  6173. const descriptors = this._cache = this._createDescriptors(chart);
  6174. this._notifyStateChanges(chart);
  6175. return descriptors;
  6176. }
  6177. _createDescriptors(chart, all) {
  6178. const config = chart && chart.config;
  6179. const options = valueOrDefault(config.options && config.options.plugins, {});
  6180. const plugins = allPlugins(config);
  6181. return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
  6182. }
  6183. _notifyStateChanges(chart) {
  6184. const previousDescriptors = this._oldCache || [];
  6185. const descriptors = this._cache;
  6186. const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id));
  6187. this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
  6188. this._notify(diff(descriptors, previousDescriptors), chart, 'start');
  6189. }
  6190. }
  6191. function allPlugins(config) {
  6192. const plugins = [];
  6193. const keys = Object.keys(registry.plugins.items);
  6194. for (let i = 0; i < keys.length; i++) {
  6195. plugins.push(registry.getPlugin(keys[i]));
  6196. }
  6197. const local = config.plugins || [];
  6198. for (let i = 0; i < local.length; i++) {
  6199. const plugin = local[i];
  6200. if (plugins.indexOf(plugin) === -1) {
  6201. plugins.push(plugin);
  6202. }
  6203. }
  6204. return plugins;
  6205. }
  6206. function getOpts(options, all) {
  6207. if (!all && options === false) {
  6208. return null;
  6209. }
  6210. if (options === true) {
  6211. return {};
  6212. }
  6213. return options;
  6214. }
  6215. function createDescriptors(chart, plugins, options, all) {
  6216. const result = [];
  6217. const context = chart.getContext();
  6218. for (let i = 0; i < plugins.length; i++) {
  6219. const plugin = plugins[i];
  6220. const id = plugin.id;
  6221. const opts = getOpts(options[id], all);
  6222. if (opts === null) {
  6223. continue;
  6224. }
  6225. result.push({
  6226. plugin,
  6227. options: pluginOpts(chart.config, plugin, opts, context)
  6228. });
  6229. }
  6230. return result;
  6231. }
  6232. function pluginOpts(config, plugin, opts, context) {
  6233. const keys = config.pluginScopeKeys(plugin);
  6234. const scopes = config.getOptionScopes(opts, keys);
  6235. return config.createResolver(scopes, context, [''], {scriptable: false, indexable: false, allKeys: true});
  6236. }
  6237. function getIndexAxis(type, options) {
  6238. const datasetDefaults = defaults.datasets[type] || {};
  6239. const datasetOptions = (options.datasets || {})[type] || {};
  6240. return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  6241. }
  6242. function getAxisFromDefaultScaleID(id, indexAxis) {
  6243. let axis = id;
  6244. if (id === '_index_') {
  6245. axis = indexAxis;
  6246. } else if (id === '_value_') {
  6247. axis = indexAxis === 'x' ? 'y' : 'x';
  6248. }
  6249. return axis;
  6250. }
  6251. function getDefaultScaleIDFromAxis(axis, indexAxis) {
  6252. return axis === indexAxis ? '_index_' : '_value_';
  6253. }
  6254. function axisFromPosition(position) {
  6255. if (position === 'top' || position === 'bottom') {
  6256. return 'x';
  6257. }
  6258. if (position === 'left' || position === 'right') {
  6259. return 'y';
  6260. }
  6261. }
  6262. function determineAxis(id, scaleOptions) {
  6263. if (id === 'x' || id === 'y') {
  6264. return id;
  6265. }
  6266. return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase();
  6267. }
  6268. function mergeScaleConfig(config, options) {
  6269. const chartDefaults = overrides[config.type] || {scales: {}};
  6270. const configScales = options.scales || {};
  6271. const chartIndexAxis = getIndexAxis(config.type, options);
  6272. const firstIDs = Object.create(null);
  6273. const scales = Object.create(null);
  6274. Object.keys(configScales).forEach(id => {
  6275. const scaleConf = configScales[id];
  6276. if (!isObject(scaleConf)) {
  6277. return console.error(`Invalid scale configuration for scale: ${id}`);
  6278. }
  6279. if (scaleConf._proxy) {
  6280. return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
  6281. }
  6282. const axis = determineAxis(id, scaleConf);
  6283. const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
  6284. const defaultScaleOptions = chartDefaults.scales || {};
  6285. firstIDs[axis] = firstIDs[axis] || id;
  6286. scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);
  6287. });
  6288. config.data.datasets.forEach(dataset => {
  6289. const type = dataset.type || config.type;
  6290. const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
  6291. const datasetDefaults = overrides[type] || {};
  6292. const defaultScaleOptions = datasetDefaults.scales || {};
  6293. Object.keys(defaultScaleOptions).forEach(defaultID => {
  6294. const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
  6295. const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis;
  6296. scales[id] = scales[id] || Object.create(null);
  6297. mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);
  6298. });
  6299. });
  6300. Object.keys(scales).forEach(key => {
  6301. const scale = scales[key];
  6302. mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
  6303. });
  6304. return scales;
  6305. }
  6306. function initOptions(config) {
  6307. const options = config.options || (config.options = {});
  6308. options.plugins = valueOrDefault(options.plugins, {});
  6309. options.scales = mergeScaleConfig(config, options);
  6310. }
  6311. function initData(data) {
  6312. data = data || {};
  6313. data.datasets = data.datasets || [];
  6314. data.labels = data.labels || [];
  6315. return data;
  6316. }
  6317. function initConfig(config) {
  6318. config = config || {};
  6319. config.data = initData(config.data);
  6320. initOptions(config);
  6321. return config;
  6322. }
  6323. const keyCache = new Map();
  6324. const keysCached = new Set();
  6325. function cachedKeys(cacheKey, generate) {
  6326. let keys = keyCache.get(cacheKey);
  6327. if (!keys) {
  6328. keys = generate();
  6329. keyCache.set(cacheKey, keys);
  6330. keysCached.add(keys);
  6331. }
  6332. return keys;
  6333. }
  6334. const addIfFound = (set, obj, key) => {
  6335. const opts = resolveObjectKey(obj, key);
  6336. if (opts !== undefined) {
  6337. set.add(opts);
  6338. }
  6339. };
  6340. class Config {
  6341. constructor(config) {
  6342. this._config = initConfig(config);
  6343. this._scopeCache = new Map();
  6344. this._resolverCache = new Map();
  6345. }
  6346. get platform() {
  6347. return this._config.platform;
  6348. }
  6349. get type() {
  6350. return this._config.type;
  6351. }
  6352. set type(type) {
  6353. this._config.type = type;
  6354. }
  6355. get data() {
  6356. return this._config.data;
  6357. }
  6358. set data(data) {
  6359. this._config.data = initData(data);
  6360. }
  6361. get options() {
  6362. return this._config.options;
  6363. }
  6364. set options(options) {
  6365. this._config.options = options;
  6366. }
  6367. get plugins() {
  6368. return this._config.plugins;
  6369. }
  6370. update() {
  6371. const config = this._config;
  6372. this.clearCache();
  6373. initOptions(config);
  6374. }
  6375. clearCache() {
  6376. this._scopeCache.clear();
  6377. this._resolverCache.clear();
  6378. }
  6379. datasetScopeKeys(datasetType) {
  6380. return cachedKeys(datasetType,
  6381. () => [[
  6382. `datasets.${datasetType}`,
  6383. ''
  6384. ]]);
  6385. }
  6386. datasetAnimationScopeKeys(datasetType, transition) {
  6387. return cachedKeys(`${datasetType}.transition.${transition}`,
  6388. () => [
  6389. [
  6390. `datasets.${datasetType}.transitions.${transition}`,
  6391. `transitions.${transition}`,
  6392. ],
  6393. [
  6394. `datasets.${datasetType}`,
  6395. ''
  6396. ]
  6397. ]);
  6398. }
  6399. datasetElementScopeKeys(datasetType, elementType) {
  6400. return cachedKeys(`${datasetType}-${elementType}`,
  6401. () => [[
  6402. `datasets.${datasetType}.elements.${elementType}`,
  6403. `datasets.${datasetType}`,
  6404. `elements.${elementType}`,
  6405. ''
  6406. ]]);
  6407. }
  6408. pluginScopeKeys(plugin) {
  6409. const id = plugin.id;
  6410. const type = this.type;
  6411. return cachedKeys(`${type}-plugin-${id}`,
  6412. () => [[
  6413. `plugins.${id}`,
  6414. ...plugin.additionalOptionScopes || [],
  6415. ]]);
  6416. }
  6417. _cachedScopes(mainScope, resetCache) {
  6418. const _scopeCache = this._scopeCache;
  6419. let cache = _scopeCache.get(mainScope);
  6420. if (!cache || resetCache) {
  6421. cache = new Map();
  6422. _scopeCache.set(mainScope, cache);
  6423. }
  6424. return cache;
  6425. }
  6426. getOptionScopes(mainScope, keyLists, resetCache) {
  6427. const {options, type} = this;
  6428. const cache = this._cachedScopes(mainScope, resetCache);
  6429. const cached = cache.get(keyLists);
  6430. if (cached) {
  6431. return cached;
  6432. }
  6433. const scopes = new Set();
  6434. keyLists.forEach(keys => {
  6435. if (mainScope) {
  6436. scopes.add(mainScope);
  6437. keys.forEach(key => addIfFound(scopes, mainScope, key));
  6438. }
  6439. keys.forEach(key => addIfFound(scopes, options, key));
  6440. keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));
  6441. keys.forEach(key => addIfFound(scopes, defaults, key));
  6442. keys.forEach(key => addIfFound(scopes, descriptors, key));
  6443. });
  6444. const array = Array.from(scopes);
  6445. if (array.length === 0) {
  6446. array.push(Object.create(null));
  6447. }
  6448. if (keysCached.has(keyLists)) {
  6449. cache.set(keyLists, array);
  6450. }
  6451. return array;
  6452. }
  6453. chartOptionScopes() {
  6454. const {options, type} = this;
  6455. return [
  6456. options,
  6457. overrides[type] || {},
  6458. defaults.datasets[type] || {},
  6459. {type},
  6460. defaults,
  6461. descriptors
  6462. ];
  6463. }
  6464. resolveNamedOptions(scopes, names, context, prefixes = ['']) {
  6465. const result = {$shared: true};
  6466. const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
  6467. let options = resolver;
  6468. if (needContext(resolver, names)) {
  6469. result.$shared = false;
  6470. context = isFunction(context) ? context() : context;
  6471. const subResolver = this.createResolver(scopes, context, subPrefixes);
  6472. options = _attachContext(resolver, context, subResolver);
  6473. }
  6474. for (const prop of names) {
  6475. result[prop] = options[prop];
  6476. }
  6477. return result;
  6478. }
  6479. createResolver(scopes, context, prefixes = [''], descriptorDefaults) {
  6480. const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
  6481. return isObject(context)
  6482. ? _attachContext(resolver, context, undefined, descriptorDefaults)
  6483. : resolver;
  6484. }
  6485. }
  6486. function getResolver(resolverCache, scopes, prefixes) {
  6487. let cache = resolverCache.get(scopes);
  6488. if (!cache) {
  6489. cache = new Map();
  6490. resolverCache.set(scopes, cache);
  6491. }
  6492. const cacheKey = prefixes.join();
  6493. let cached = cache.get(cacheKey);
  6494. if (!cached) {
  6495. const resolver = _createResolver(scopes, prefixes);
  6496. cached = {
  6497. resolver,
  6498. subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover'))
  6499. };
  6500. cache.set(cacheKey, cached);
  6501. }
  6502. return cached;
  6503. }
  6504. const hasFunction = value => isObject(value)
  6505. && Object.getOwnPropertyNames(value).reduce((acc, key) => acc || isFunction(value[key]), false);
  6506. function needContext(proxy, names) {
  6507. const {isScriptable, isIndexable} = _descriptors(proxy);
  6508. for (const prop of names) {
  6509. const scriptable = isScriptable(prop);
  6510. const indexable = isIndexable(prop);
  6511. const value = (indexable || scriptable) && proxy[prop];
  6512. if ((scriptable && (isFunction(value) || hasFunction(value)))
  6513. || (indexable && isArray(value))) {
  6514. return true;
  6515. }
  6516. }
  6517. return false;
  6518. }
  6519. var version = "3.7.1";
  6520. const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
  6521. function positionIsHorizontal(position, axis) {
  6522. return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');
  6523. }
  6524. function compare2Level(l1, l2) {
  6525. return function(a, b) {
  6526. return a[l1] === b[l1]
  6527. ? a[l2] - b[l2]
  6528. : a[l1] - b[l1];
  6529. };
  6530. }
  6531. function onAnimationsComplete(context) {
  6532. const chart = context.chart;
  6533. const animationOptions = chart.options.animation;
  6534. chart.notifyPlugins('afterRender');
  6535. callback(animationOptions && animationOptions.onComplete, [context], chart);
  6536. }
  6537. function onAnimationProgress(context) {
  6538. const chart = context.chart;
  6539. const animationOptions = chart.options.animation;
  6540. callback(animationOptions && animationOptions.onProgress, [context], chart);
  6541. }
  6542. function getCanvas(item) {
  6543. if (_isDomSupported() && typeof item === 'string') {
  6544. item = document.getElementById(item);
  6545. } else if (item && item.length) {
  6546. item = item[0];
  6547. }
  6548. if (item && item.canvas) {
  6549. item = item.canvas;
  6550. }
  6551. return item;
  6552. }
  6553. const instances = {};
  6554. const getChart = (key) => {
  6555. const canvas = getCanvas(key);
  6556. return Object.values(instances).filter((c) => c.canvas === canvas).pop();
  6557. };
  6558. function moveNumericKeys(obj, start, move) {
  6559. const keys = Object.keys(obj);
  6560. for (const key of keys) {
  6561. const intKey = +key;
  6562. if (intKey >= start) {
  6563. const value = obj[key];
  6564. delete obj[key];
  6565. if (move > 0 || intKey > start) {
  6566. obj[intKey + move] = value;
  6567. }
  6568. }
  6569. }
  6570. }
  6571. function determineLastEvent(e, lastEvent, inChartArea, isClick) {
  6572. if (!inChartArea || e.type === 'mouseout') {
  6573. return null;
  6574. }
  6575. if (isClick) {
  6576. return lastEvent;
  6577. }
  6578. return e;
  6579. }
  6580. class Chart {
  6581. constructor(item, userConfig) {
  6582. const config = this.config = new Config(userConfig);
  6583. const initialCanvas = getCanvas(item);
  6584. const existingChart = getChart(initialCanvas);
  6585. if (existingChart) {
  6586. throw new Error(
  6587. 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' +
  6588. ' must be destroyed before the canvas can be reused.'
  6589. );
  6590. }
  6591. const options = config.createResolver(config.chartOptionScopes(), this.getContext());
  6592. this.platform = new (config.platform || _detectPlatform(initialCanvas))();
  6593. this.platform.updateConfig(config);
  6594. const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
  6595. const canvas = context && context.canvas;
  6596. const height = canvas && canvas.height;
  6597. const width = canvas && canvas.width;
  6598. this.id = uid();
  6599. this.ctx = context;
  6600. this.canvas = canvas;
  6601. this.width = width;
  6602. this.height = height;
  6603. this._options = options;
  6604. this._aspectRatio = this.aspectRatio;
  6605. this._layers = [];
  6606. this._metasets = [];
  6607. this._stacks = undefined;
  6608. this.boxes = [];
  6609. this.currentDevicePixelRatio = undefined;
  6610. this.chartArea = undefined;
  6611. this._active = [];
  6612. this._lastEvent = undefined;
  6613. this._listeners = {};
  6614. this._responsiveListeners = undefined;
  6615. this._sortedMetasets = [];
  6616. this.scales = {};
  6617. this._plugins = new PluginService();
  6618. this.$proxies = {};
  6619. this._hiddenIndices = {};
  6620. this.attached = false;
  6621. this._animationsDisabled = undefined;
  6622. this.$context = undefined;
  6623. this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0);
  6624. this._dataChanges = [];
  6625. instances[this.id] = this;
  6626. if (!context || !canvas) {
  6627. console.error("Failed to create chart: can't acquire context from the given item");
  6628. return;
  6629. }
  6630. animator.listen(this, 'complete', onAnimationsComplete);
  6631. animator.listen(this, 'progress', onAnimationProgress);
  6632. this._initialize();
  6633. if (this.attached) {
  6634. this.update();
  6635. }
  6636. }
  6637. get aspectRatio() {
  6638. const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this;
  6639. if (!isNullOrUndef(aspectRatio)) {
  6640. return aspectRatio;
  6641. }
  6642. if (maintainAspectRatio && _aspectRatio) {
  6643. return _aspectRatio;
  6644. }
  6645. return height ? width / height : null;
  6646. }
  6647. get data() {
  6648. return this.config.data;
  6649. }
  6650. set data(data) {
  6651. this.config.data = data;
  6652. }
  6653. get options() {
  6654. return this._options;
  6655. }
  6656. set options(options) {
  6657. this.config.options = options;
  6658. }
  6659. _initialize() {
  6660. this.notifyPlugins('beforeInit');
  6661. if (this.options.responsive) {
  6662. this.resize();
  6663. } else {
  6664. retinaScale(this, this.options.devicePixelRatio);
  6665. }
  6666. this.bindEvents();
  6667. this.notifyPlugins('afterInit');
  6668. return this;
  6669. }
  6670. clear() {
  6671. clearCanvas(this.canvas, this.ctx);
  6672. return this;
  6673. }
  6674. stop() {
  6675. animator.stop(this);
  6676. return this;
  6677. }
  6678. resize(width, height) {
  6679. if (!animator.running(this)) {
  6680. this._resize(width, height);
  6681. } else {
  6682. this._resizeBeforeDraw = {width, height};
  6683. }
  6684. }
  6685. _resize(width, height) {
  6686. const options = this.options;
  6687. const canvas = this.canvas;
  6688. const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
  6689. const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
  6690. const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
  6691. const mode = this.width ? 'resize' : 'attach';
  6692. this.width = newSize.width;
  6693. this.height = newSize.height;
  6694. this._aspectRatio = this.aspectRatio;
  6695. if (!retinaScale(this, newRatio, true)) {
  6696. return;
  6697. }
  6698. this.notifyPlugins('resize', {size: newSize});
  6699. callback(options.onResize, [this, newSize], this);
  6700. if (this.attached) {
  6701. if (this._doResize(mode)) {
  6702. this.render();
  6703. }
  6704. }
  6705. }
  6706. ensureScalesHaveIDs() {
  6707. const options = this.options;
  6708. const scalesOptions = options.scales || {};
  6709. each(scalesOptions, (axisOptions, axisID) => {
  6710. axisOptions.id = axisID;
  6711. });
  6712. }
  6713. buildOrUpdateScales() {
  6714. const options = this.options;
  6715. const scaleOpts = options.scales;
  6716. const scales = this.scales;
  6717. const updated = Object.keys(scales).reduce((obj, id) => {
  6718. obj[id] = false;
  6719. return obj;
  6720. }, {});
  6721. let items = [];
  6722. if (scaleOpts) {
  6723. items = items.concat(
  6724. Object.keys(scaleOpts).map((id) => {
  6725. const scaleOptions = scaleOpts[id];
  6726. const axis = determineAxis(id, scaleOptions);
  6727. const isRadial = axis === 'r';
  6728. const isHorizontal = axis === 'x';
  6729. return {
  6730. options: scaleOptions,
  6731. dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
  6732. dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
  6733. };
  6734. })
  6735. );
  6736. }
  6737. each(items, (item) => {
  6738. const scaleOptions = item.options;
  6739. const id = scaleOptions.id;
  6740. const axis = determineAxis(id, scaleOptions);
  6741. const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
  6742. if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
  6743. scaleOptions.position = item.dposition;
  6744. }
  6745. updated[id] = true;
  6746. let scale = null;
  6747. if (id in scales && scales[id].type === scaleType) {
  6748. scale = scales[id];
  6749. } else {
  6750. const scaleClass = registry.getScale(scaleType);
  6751. scale = new scaleClass({
  6752. id,
  6753. type: scaleType,
  6754. ctx: this.ctx,
  6755. chart: this
  6756. });
  6757. scales[scale.id] = scale;
  6758. }
  6759. scale.init(scaleOptions, options);
  6760. });
  6761. each(updated, (hasUpdated, id) => {
  6762. if (!hasUpdated) {
  6763. delete scales[id];
  6764. }
  6765. });
  6766. each(scales, (scale) => {
  6767. layouts.configure(this, scale, scale.options);
  6768. layouts.addBox(this, scale);
  6769. });
  6770. }
  6771. _updateMetasets() {
  6772. const metasets = this._metasets;
  6773. const numData = this.data.datasets.length;
  6774. const numMeta = metasets.length;
  6775. metasets.sort((a, b) => a.index - b.index);
  6776. if (numMeta > numData) {
  6777. for (let i = numData; i < numMeta; ++i) {
  6778. this._destroyDatasetMeta(i);
  6779. }
  6780. metasets.splice(numData, numMeta - numData);
  6781. }
  6782. this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
  6783. }
  6784. _removeUnreferencedMetasets() {
  6785. const {_metasets: metasets, data: {datasets}} = this;
  6786. if (metasets.length > datasets.length) {
  6787. delete this._stacks;
  6788. }
  6789. metasets.forEach((meta, index) => {
  6790. if (datasets.filter(x => x === meta._dataset).length === 0) {
  6791. this._destroyDatasetMeta(index);
  6792. }
  6793. });
  6794. }
  6795. buildOrUpdateControllers() {
  6796. const newControllers = [];
  6797. const datasets = this.data.datasets;
  6798. let i, ilen;
  6799. this._removeUnreferencedMetasets();
  6800. for (i = 0, ilen = datasets.length; i < ilen; i++) {
  6801. const dataset = datasets[i];
  6802. let meta = this.getDatasetMeta(i);
  6803. const type = dataset.type || this.config.type;
  6804. if (meta.type && meta.type !== type) {
  6805. this._destroyDatasetMeta(i);
  6806. meta = this.getDatasetMeta(i);
  6807. }
  6808. meta.type = type;
  6809. meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
  6810. meta.order = dataset.order || 0;
  6811. meta.index = i;
  6812. meta.label = '' + dataset.label;
  6813. meta.visible = this.isDatasetVisible(i);
  6814. if (meta.controller) {
  6815. meta.controller.updateIndex(i);
  6816. meta.controller.linkScales();
  6817. } else {
  6818. const ControllerClass = registry.getController(type);
  6819. const {datasetElementType, dataElementType} = defaults.datasets[type];
  6820. Object.assign(ControllerClass.prototype, {
  6821. dataElementType: registry.getElement(dataElementType),
  6822. datasetElementType: datasetElementType && registry.getElement(datasetElementType)
  6823. });
  6824. meta.controller = new ControllerClass(this, i);
  6825. newControllers.push(meta.controller);
  6826. }
  6827. }
  6828. this._updateMetasets();
  6829. return newControllers;
  6830. }
  6831. _resetElements() {
  6832. each(this.data.datasets, (dataset, datasetIndex) => {
  6833. this.getDatasetMeta(datasetIndex).controller.reset();
  6834. }, this);
  6835. }
  6836. reset() {
  6837. this._resetElements();
  6838. this.notifyPlugins('reset');
  6839. }
  6840. update(mode) {
  6841. const config = this.config;
  6842. config.update();
  6843. const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
  6844. const animsDisabled = this._animationsDisabled = !options.animation;
  6845. this._updateScales();
  6846. this._checkEventBindings();
  6847. this._updateHiddenIndices();
  6848. this._plugins.invalidate();
  6849. if (this.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) {
  6850. return;
  6851. }
  6852. const newControllers = this.buildOrUpdateControllers();
  6853. this.notifyPlugins('beforeElementsUpdate');
  6854. let minPadding = 0;
  6855. for (let i = 0, ilen = this.data.datasets.length; i < ilen; i++) {
  6856. const {controller} = this.getDatasetMeta(i);
  6857. const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
  6858. controller.buildOrUpdateElements(reset);
  6859. minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
  6860. }
  6861. minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
  6862. this._updateLayout(minPadding);
  6863. if (!animsDisabled) {
  6864. each(newControllers, (controller) => {
  6865. controller.reset();
  6866. });
  6867. }
  6868. this._updateDatasets(mode);
  6869. this.notifyPlugins('afterUpdate', {mode});
  6870. this._layers.sort(compare2Level('z', '_idx'));
  6871. const {_active, _lastEvent} = this;
  6872. if (_lastEvent) {
  6873. this._eventHandler(_lastEvent, true);
  6874. } else if (_active.length) {
  6875. this._updateHoverStyles(_active, _active, true);
  6876. }
  6877. this.render();
  6878. }
  6879. _updateScales() {
  6880. each(this.scales, (scale) => {
  6881. layouts.removeBox(this, scale);
  6882. });
  6883. this.ensureScalesHaveIDs();
  6884. this.buildOrUpdateScales();
  6885. }
  6886. _checkEventBindings() {
  6887. const options = this.options;
  6888. const existingEvents = new Set(Object.keys(this._listeners));
  6889. const newEvents = new Set(options.events);
  6890. if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
  6891. this.unbindEvents();
  6892. this.bindEvents();
  6893. }
  6894. }
  6895. _updateHiddenIndices() {
  6896. const {_hiddenIndices} = this;
  6897. const changes = this._getUniformDataChanges() || [];
  6898. for (const {method, start, count} of changes) {
  6899. const move = method === '_removeElements' ? -count : count;
  6900. moveNumericKeys(_hiddenIndices, start, move);
  6901. }
  6902. }
  6903. _getUniformDataChanges() {
  6904. const _dataChanges = this._dataChanges;
  6905. if (!_dataChanges || !_dataChanges.length) {
  6906. return;
  6907. }
  6908. this._dataChanges = [];
  6909. const datasetCount = this.data.datasets.length;
  6910. const makeSet = (idx) => new Set(
  6911. _dataChanges
  6912. .filter(c => c[0] === idx)
  6913. .map((c, i) => i + ',' + c.splice(1).join(','))
  6914. );
  6915. const changeSet = makeSet(0);
  6916. for (let i = 1; i < datasetCount; i++) {
  6917. if (!setsEqual(changeSet, makeSet(i))) {
  6918. return;
  6919. }
  6920. }
  6921. return Array.from(changeSet)
  6922. .map(c => c.split(','))
  6923. .map(a => ({method: a[1], start: +a[2], count: +a[3]}));
  6924. }
  6925. _updateLayout(minPadding) {
  6926. if (this.notifyPlugins('beforeLayout', {cancelable: true}) === false) {
  6927. return;
  6928. }
  6929. layouts.update(this, this.width, this.height, minPadding);
  6930. const area = this.chartArea;
  6931. const noArea = area.width <= 0 || area.height <= 0;
  6932. this._layers = [];
  6933. each(this.boxes, (box) => {
  6934. if (noArea && box.position === 'chartArea') {
  6935. return;
  6936. }
  6937. if (box.configure) {
  6938. box.configure();
  6939. }
  6940. this._layers.push(...box._layers());
  6941. }, this);
  6942. this._layers.forEach((item, index) => {
  6943. item._idx = index;
  6944. });
  6945. this.notifyPlugins('afterLayout');
  6946. }
  6947. _updateDatasets(mode) {
  6948. if (this.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) {
  6949. return;
  6950. }
  6951. for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  6952. this.getDatasetMeta(i).controller.configure();
  6953. }
  6954. for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  6955. this._updateDataset(i, isFunction(mode) ? mode({datasetIndex: i}) : mode);
  6956. }
  6957. this.notifyPlugins('afterDatasetsUpdate', {mode});
  6958. }
  6959. _updateDataset(index, mode) {
  6960. const meta = this.getDatasetMeta(index);
  6961. const args = {meta, index, mode, cancelable: true};
  6962. if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
  6963. return;
  6964. }
  6965. meta.controller._update(mode);
  6966. args.cancelable = false;
  6967. this.notifyPlugins('afterDatasetUpdate', args);
  6968. }
  6969. render() {
  6970. if (this.notifyPlugins('beforeRender', {cancelable: true}) === false) {
  6971. return;
  6972. }
  6973. if (animator.has(this)) {
  6974. if (this.attached && !animator.running(this)) {
  6975. animator.start(this);
  6976. }
  6977. } else {
  6978. this.draw();
  6979. onAnimationsComplete({chart: this});
  6980. }
  6981. }
  6982. draw() {
  6983. let i;
  6984. if (this._resizeBeforeDraw) {
  6985. const {width, height} = this._resizeBeforeDraw;
  6986. this._resize(width, height);
  6987. this._resizeBeforeDraw = null;
  6988. }
  6989. this.clear();
  6990. if (this.width <= 0 || this.height <= 0) {
  6991. return;
  6992. }
  6993. if (this.notifyPlugins('beforeDraw', {cancelable: true}) === false) {
  6994. return;
  6995. }
  6996. const layers = this._layers;
  6997. for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
  6998. layers[i].draw(this.chartArea);
  6999. }
  7000. this._drawDatasets();
  7001. for (; i < layers.length; ++i) {
  7002. layers[i].draw(this.chartArea);
  7003. }
  7004. this.notifyPlugins('afterDraw');
  7005. }
  7006. _getSortedDatasetMetas(filterVisible) {
  7007. const metasets = this._sortedMetasets;
  7008. const result = [];
  7009. let i, ilen;
  7010. for (i = 0, ilen = metasets.length; i < ilen; ++i) {
  7011. const meta = metasets[i];
  7012. if (!filterVisible || meta.visible) {
  7013. result.push(meta);
  7014. }
  7015. }
  7016. return result;
  7017. }
  7018. getSortedVisibleDatasetMetas() {
  7019. return this._getSortedDatasetMetas(true);
  7020. }
  7021. _drawDatasets() {
  7022. if (this.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) {
  7023. return;
  7024. }
  7025. const metasets = this.getSortedVisibleDatasetMetas();
  7026. for (let i = metasets.length - 1; i >= 0; --i) {
  7027. this._drawDataset(metasets[i]);
  7028. }
  7029. this.notifyPlugins('afterDatasetsDraw');
  7030. }
  7031. _drawDataset(meta) {
  7032. const ctx = this.ctx;
  7033. const clip = meta._clip;
  7034. const useClip = !clip.disabled;
  7035. const area = this.chartArea;
  7036. const args = {
  7037. meta,
  7038. index: meta.index,
  7039. cancelable: true
  7040. };
  7041. if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
  7042. return;
  7043. }
  7044. if (useClip) {
  7045. clipArea(ctx, {
  7046. left: clip.left === false ? 0 : area.left - clip.left,
  7047. right: clip.right === false ? this.width : area.right + clip.right,
  7048. top: clip.top === false ? 0 : area.top - clip.top,
  7049. bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
  7050. });
  7051. }
  7052. meta.controller.draw();
  7053. if (useClip) {
  7054. unclipArea(ctx);
  7055. }
  7056. args.cancelable = false;
  7057. this.notifyPlugins('afterDatasetDraw', args);
  7058. }
  7059. isPointInArea(point) {
  7060. return _isPointInArea(point, this.chartArea, this._minPadding);
  7061. }
  7062. getElementsAtEventForMode(e, mode, options, useFinalPosition) {
  7063. const method = Interaction.modes[mode];
  7064. if (typeof method === 'function') {
  7065. return method(this, e, options, useFinalPosition);
  7066. }
  7067. return [];
  7068. }
  7069. getDatasetMeta(datasetIndex) {
  7070. const dataset = this.data.datasets[datasetIndex];
  7071. const metasets = this._metasets;
  7072. let meta = metasets.filter(x => x && x._dataset === dataset).pop();
  7073. if (!meta) {
  7074. meta = {
  7075. type: null,
  7076. data: [],
  7077. dataset: null,
  7078. controller: null,
  7079. hidden: null,
  7080. xAxisID: null,
  7081. yAxisID: null,
  7082. order: dataset && dataset.order || 0,
  7083. index: datasetIndex,
  7084. _dataset: dataset,
  7085. _parsed: [],
  7086. _sorted: false
  7087. };
  7088. metasets.push(meta);
  7089. }
  7090. return meta;
  7091. }
  7092. getContext() {
  7093. return this.$context || (this.$context = createContext(null, {chart: this, type: 'chart'}));
  7094. }
  7095. getVisibleDatasetCount() {
  7096. return this.getSortedVisibleDatasetMetas().length;
  7097. }
  7098. isDatasetVisible(datasetIndex) {
  7099. const dataset = this.data.datasets[datasetIndex];
  7100. if (!dataset) {
  7101. return false;
  7102. }
  7103. const meta = this.getDatasetMeta(datasetIndex);
  7104. return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
  7105. }
  7106. setDatasetVisibility(datasetIndex, visible) {
  7107. const meta = this.getDatasetMeta(datasetIndex);
  7108. meta.hidden = !visible;
  7109. }
  7110. toggleDataVisibility(index) {
  7111. this._hiddenIndices[index] = !this._hiddenIndices[index];
  7112. }
  7113. getDataVisibility(index) {
  7114. return !this._hiddenIndices[index];
  7115. }
  7116. _updateVisibility(datasetIndex, dataIndex, visible) {
  7117. const mode = visible ? 'show' : 'hide';
  7118. const meta = this.getDatasetMeta(datasetIndex);
  7119. const anims = meta.controller._resolveAnimations(undefined, mode);
  7120. if (defined(dataIndex)) {
  7121. meta.data[dataIndex].hidden = !visible;
  7122. this.update();
  7123. } else {
  7124. this.setDatasetVisibility(datasetIndex, visible);
  7125. anims.update(meta, {visible});
  7126. this.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);
  7127. }
  7128. }
  7129. hide(datasetIndex, dataIndex) {
  7130. this._updateVisibility(datasetIndex, dataIndex, false);
  7131. }
  7132. show(datasetIndex, dataIndex) {
  7133. this._updateVisibility(datasetIndex, dataIndex, true);
  7134. }
  7135. _destroyDatasetMeta(datasetIndex) {
  7136. const meta = this._metasets[datasetIndex];
  7137. if (meta && meta.controller) {
  7138. meta.controller._destroy();
  7139. }
  7140. delete this._metasets[datasetIndex];
  7141. }
  7142. _stop() {
  7143. let i, ilen;
  7144. this.stop();
  7145. animator.remove(this);
  7146. for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
  7147. this._destroyDatasetMeta(i);
  7148. }
  7149. }
  7150. destroy() {
  7151. this.notifyPlugins('beforeDestroy');
  7152. const {canvas, ctx} = this;
  7153. this._stop();
  7154. this.config.clearCache();
  7155. if (canvas) {
  7156. this.unbindEvents();
  7157. clearCanvas(canvas, ctx);
  7158. this.platform.releaseContext(ctx);
  7159. this.canvas = null;
  7160. this.ctx = null;
  7161. }
  7162. this.notifyPlugins('destroy');
  7163. delete instances[this.id];
  7164. this.notifyPlugins('afterDestroy');
  7165. }
  7166. toBase64Image(...args) {
  7167. return this.canvas.toDataURL(...args);
  7168. }
  7169. bindEvents() {
  7170. this.bindUserEvents();
  7171. if (this.options.responsive) {
  7172. this.bindResponsiveEvents();
  7173. } else {
  7174. this.attached = true;
  7175. }
  7176. }
  7177. bindUserEvents() {
  7178. const listeners = this._listeners;
  7179. const platform = this.platform;
  7180. const _add = (type, listener) => {
  7181. platform.addEventListener(this, type, listener);
  7182. listeners[type] = listener;
  7183. };
  7184. const listener = (e, x, y) => {
  7185. e.offsetX = x;
  7186. e.offsetY = y;
  7187. this._eventHandler(e);
  7188. };
  7189. each(this.options.events, (type) => _add(type, listener));
  7190. }
  7191. bindResponsiveEvents() {
  7192. if (!this._responsiveListeners) {
  7193. this._responsiveListeners = {};
  7194. }
  7195. const listeners = this._responsiveListeners;
  7196. const platform = this.platform;
  7197. const _add = (type, listener) => {
  7198. platform.addEventListener(this, type, listener);
  7199. listeners[type] = listener;
  7200. };
  7201. const _remove = (type, listener) => {
  7202. if (listeners[type]) {
  7203. platform.removeEventListener(this, type, listener);
  7204. delete listeners[type];
  7205. }
  7206. };
  7207. const listener = (width, height) => {
  7208. if (this.canvas) {
  7209. this.resize(width, height);
  7210. }
  7211. };
  7212. let detached;
  7213. const attached = () => {
  7214. _remove('attach', attached);
  7215. this.attached = true;
  7216. this.resize();
  7217. _add('resize', listener);
  7218. _add('detach', detached);
  7219. };
  7220. detached = () => {
  7221. this.attached = false;
  7222. _remove('resize', listener);
  7223. this._stop();
  7224. this._resize(0, 0);
  7225. _add('attach', attached);
  7226. };
  7227. if (platform.isAttached(this.canvas)) {
  7228. attached();
  7229. } else {
  7230. detached();
  7231. }
  7232. }
  7233. unbindEvents() {
  7234. each(this._listeners, (listener, type) => {
  7235. this.platform.removeEventListener(this, type, listener);
  7236. });
  7237. this._listeners = {};
  7238. each(this._responsiveListeners, (listener, type) => {
  7239. this.platform.removeEventListener(this, type, listener);
  7240. });
  7241. this._responsiveListeners = undefined;
  7242. }
  7243. updateHoverStyle(items, mode, enabled) {
  7244. const prefix = enabled ? 'set' : 'remove';
  7245. let meta, item, i, ilen;
  7246. if (mode === 'dataset') {
  7247. meta = this.getDatasetMeta(items[0].datasetIndex);
  7248. meta.controller['_' + prefix + 'DatasetHoverStyle']();
  7249. }
  7250. for (i = 0, ilen = items.length; i < ilen; ++i) {
  7251. item = items[i];
  7252. const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
  7253. if (controller) {
  7254. controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
  7255. }
  7256. }
  7257. }
  7258. getActiveElements() {
  7259. return this._active || [];
  7260. }
  7261. setActiveElements(activeElements) {
  7262. const lastActive = this._active || [];
  7263. const active = activeElements.map(({datasetIndex, index}) => {
  7264. const meta = this.getDatasetMeta(datasetIndex);
  7265. if (!meta) {
  7266. throw new Error('No dataset found at index ' + datasetIndex);
  7267. }
  7268. return {
  7269. datasetIndex,
  7270. element: meta.data[index],
  7271. index,
  7272. };
  7273. });
  7274. const changed = !_elementsEqual(active, lastActive);
  7275. if (changed) {
  7276. this._active = active;
  7277. this._lastEvent = null;
  7278. this._updateHoverStyles(active, lastActive);
  7279. }
  7280. }
  7281. notifyPlugins(hook, args, filter) {
  7282. return this._plugins.notify(this, hook, args, filter);
  7283. }
  7284. _updateHoverStyles(active, lastActive, replay) {
  7285. const hoverOptions = this.options.hover;
  7286. const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index));
  7287. const deactivated = diff(lastActive, active);
  7288. const activated = replay ? active : diff(active, lastActive);
  7289. if (deactivated.length) {
  7290. this.updateHoverStyle(deactivated, hoverOptions.mode, false);
  7291. }
  7292. if (activated.length && hoverOptions.mode) {
  7293. this.updateHoverStyle(activated, hoverOptions.mode, true);
  7294. }
  7295. }
  7296. _eventHandler(e, replay) {
  7297. const args = {
  7298. event: e,
  7299. replay,
  7300. cancelable: true,
  7301. inChartArea: this.isPointInArea(e)
  7302. };
  7303. const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type);
  7304. if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
  7305. return;
  7306. }
  7307. const changed = this._handleEvent(e, replay, args.inChartArea);
  7308. args.cancelable = false;
  7309. this.notifyPlugins('afterEvent', args, eventFilter);
  7310. if (changed || args.changed) {
  7311. this.render();
  7312. }
  7313. return this;
  7314. }
  7315. _handleEvent(e, replay, inChartArea) {
  7316. const {_active: lastActive = [], options} = this;
  7317. const useFinalPosition = replay;
  7318. const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
  7319. const isClick = _isClickEvent(e);
  7320. const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
  7321. if (inChartArea) {
  7322. this._lastEvent = null;
  7323. callback(options.onHover, [e, active, this], this);
  7324. if (isClick) {
  7325. callback(options.onClick, [e, active, this], this);
  7326. }
  7327. }
  7328. const changed = !_elementsEqual(active, lastActive);
  7329. if (changed || replay) {
  7330. this._active = active;
  7331. this._updateHoverStyles(active, lastActive, replay);
  7332. }
  7333. this._lastEvent = lastEvent;
  7334. return changed;
  7335. }
  7336. _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
  7337. if (e.type === 'mouseout') {
  7338. return [];
  7339. }
  7340. if (!inChartArea) {
  7341. return lastActive;
  7342. }
  7343. const hoverOptions = this.options.hover;
  7344. return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
  7345. }
  7346. }
  7347. const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
  7348. const enumerable = true;
  7349. Object.defineProperties(Chart, {
  7350. defaults: {
  7351. enumerable,
  7352. value: defaults
  7353. },
  7354. instances: {
  7355. enumerable,
  7356. value: instances
  7357. },
  7358. overrides: {
  7359. enumerable,
  7360. value: overrides
  7361. },
  7362. registry: {
  7363. enumerable,
  7364. value: registry
  7365. },
  7366. version: {
  7367. enumerable,
  7368. value: version
  7369. },
  7370. getChart: {
  7371. enumerable,
  7372. value: getChart
  7373. },
  7374. register: {
  7375. enumerable,
  7376. value: (...items) => {
  7377. registry.add(...items);
  7378. invalidatePlugins();
  7379. }
  7380. },
  7381. unregister: {
  7382. enumerable,
  7383. value: (...items) => {
  7384. registry.remove(...items);
  7385. invalidatePlugins();
  7386. }
  7387. }
  7388. });
  7389. function abstract() {
  7390. throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  7391. }
  7392. class DateAdapter {
  7393. constructor(options) {
  7394. this.options = options || {};
  7395. }
  7396. formats() {
  7397. return abstract();
  7398. }
  7399. parse(value, format) {
  7400. return abstract();
  7401. }
  7402. format(timestamp, format) {
  7403. return abstract();
  7404. }
  7405. add(timestamp, amount, unit) {
  7406. return abstract();
  7407. }
  7408. diff(a, b, unit) {
  7409. return abstract();
  7410. }
  7411. startOf(timestamp, unit, weekday) {
  7412. return abstract();
  7413. }
  7414. endOf(timestamp, unit) {
  7415. return abstract();
  7416. }
  7417. }
  7418. DateAdapter.override = function(members) {
  7419. Object.assign(DateAdapter.prototype, members);
  7420. };
  7421. var _adapters = {
  7422. _date: DateAdapter
  7423. };
  7424. function getAllScaleValues(scale, type) {
  7425. if (!scale._cache.$bar) {
  7426. const visibleMetas = scale.getMatchingVisibleMetas(type);
  7427. let values = [];
  7428. for (let i = 0, ilen = visibleMetas.length; i < ilen; i++) {
  7429. values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
  7430. }
  7431. scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b));
  7432. }
  7433. return scale._cache.$bar;
  7434. }
  7435. function computeMinSampleSize(meta) {
  7436. const scale = meta.iScale;
  7437. const values = getAllScaleValues(scale, meta.type);
  7438. let min = scale._length;
  7439. let i, ilen, curr, prev;
  7440. const updateMinAndPrev = () => {
  7441. if (curr === 32767 || curr === -32768) {
  7442. return;
  7443. }
  7444. if (defined(prev)) {
  7445. min = Math.min(min, Math.abs(curr - prev) || min);
  7446. }
  7447. prev = curr;
  7448. };
  7449. for (i = 0, ilen = values.length; i < ilen; ++i) {
  7450. curr = scale.getPixelForValue(values[i]);
  7451. updateMinAndPrev();
  7452. }
  7453. prev = undefined;
  7454. for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {
  7455. curr = scale.getPixelForTick(i);
  7456. updateMinAndPrev();
  7457. }
  7458. return min;
  7459. }
  7460. function computeFitCategoryTraits(index, ruler, options, stackCount) {
  7461. const thickness = options.barThickness;
  7462. let size, ratio;
  7463. if (isNullOrUndef(thickness)) {
  7464. size = ruler.min * options.categoryPercentage;
  7465. ratio = options.barPercentage;
  7466. } else {
  7467. size = thickness * stackCount;
  7468. ratio = 1;
  7469. }
  7470. return {
  7471. chunk: size / stackCount,
  7472. ratio,
  7473. start: ruler.pixels[index] - (size / 2)
  7474. };
  7475. }
  7476. function computeFlexCategoryTraits(index, ruler, options, stackCount) {
  7477. const pixels = ruler.pixels;
  7478. const curr = pixels[index];
  7479. let prev = index > 0 ? pixels[index - 1] : null;
  7480. let next = index < pixels.length - 1 ? pixels[index + 1] : null;
  7481. const percent = options.categoryPercentage;
  7482. if (prev === null) {
  7483. prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
  7484. }
  7485. if (next === null) {
  7486. next = curr + curr - prev;
  7487. }
  7488. const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
  7489. const size = Math.abs(next - prev) / 2 * percent;
  7490. return {
  7491. chunk: size / stackCount,
  7492. ratio: options.barPercentage,
  7493. start
  7494. };
  7495. }
  7496. function parseFloatBar(entry, item, vScale, i) {
  7497. const startValue = vScale.parse(entry[0], i);
  7498. const endValue = vScale.parse(entry[1], i);
  7499. const min = Math.min(startValue, endValue);
  7500. const max = Math.max(startValue, endValue);
  7501. let barStart = min;
  7502. let barEnd = max;
  7503. if (Math.abs(min) > Math.abs(max)) {
  7504. barStart = max;
  7505. barEnd = min;
  7506. }
  7507. item[vScale.axis] = barEnd;
  7508. item._custom = {
  7509. barStart,
  7510. barEnd,
  7511. start: startValue,
  7512. end: endValue,
  7513. min,
  7514. max
  7515. };
  7516. }
  7517. function parseValue(entry, item, vScale, i) {
  7518. if (isArray(entry)) {
  7519. parseFloatBar(entry, item, vScale, i);
  7520. } else {
  7521. item[vScale.axis] = vScale.parse(entry, i);
  7522. }
  7523. return item;
  7524. }
  7525. function parseArrayOrPrimitive(meta, data, start, count) {
  7526. const iScale = meta.iScale;
  7527. const vScale = meta.vScale;
  7528. const labels = iScale.getLabels();
  7529. const singleScale = iScale === vScale;
  7530. const parsed = [];
  7531. let i, ilen, item, entry;
  7532. for (i = start, ilen = start + count; i < ilen; ++i) {
  7533. entry = data[i];
  7534. item = {};
  7535. item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
  7536. parsed.push(parseValue(entry, item, vScale, i));
  7537. }
  7538. return parsed;
  7539. }
  7540. function isFloatBar(custom) {
  7541. return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  7542. }
  7543. function barSign(size, vScale, actualBase) {
  7544. if (size !== 0) {
  7545. return sign(size);
  7546. }
  7547. return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  7548. }
  7549. function borderProps(properties) {
  7550. let reverse, start, end, top, bottom;
  7551. if (properties.horizontal) {
  7552. reverse = properties.base > properties.x;
  7553. start = 'left';
  7554. end = 'right';
  7555. } else {
  7556. reverse = properties.base < properties.y;
  7557. start = 'bottom';
  7558. end = 'top';
  7559. }
  7560. if (reverse) {
  7561. top = 'end';
  7562. bottom = 'start';
  7563. } else {
  7564. top = 'start';
  7565. bottom = 'end';
  7566. }
  7567. return {start, end, reverse, top, bottom};
  7568. }
  7569. function setBorderSkipped(properties, options, stack, index) {
  7570. let edge = options.borderSkipped;
  7571. const res = {};
  7572. if (!edge) {
  7573. properties.borderSkipped = res;
  7574. return;
  7575. }
  7576. const {start, end, reverse, top, bottom} = borderProps(properties);
  7577. if (edge === 'middle' && stack) {
  7578. properties.enableBorderRadius = true;
  7579. if ((stack._top || 0) === index) {
  7580. edge = top;
  7581. } else if ((stack._bottom || 0) === index) {
  7582. edge = bottom;
  7583. } else {
  7584. res[parseEdge(bottom, start, end, reverse)] = true;
  7585. edge = top;
  7586. }
  7587. }
  7588. res[parseEdge(edge, start, end, reverse)] = true;
  7589. properties.borderSkipped = res;
  7590. }
  7591. function parseEdge(edge, a, b, reverse) {
  7592. if (reverse) {
  7593. edge = swap(edge, a, b);
  7594. edge = startEnd(edge, b, a);
  7595. } else {
  7596. edge = startEnd(edge, a, b);
  7597. }
  7598. return edge;
  7599. }
  7600. function swap(orig, v1, v2) {
  7601. return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  7602. }
  7603. function startEnd(v, start, end) {
  7604. return v === 'start' ? start : v === 'end' ? end : v;
  7605. }
  7606. function setInflateAmount(properties, {inflateAmount}, ratio) {
  7607. properties.inflateAmount = inflateAmount === 'auto'
  7608. ? ratio === 1 ? 0.33 : 0
  7609. : inflateAmount;
  7610. }
  7611. class BarController extends DatasetController {
  7612. parsePrimitiveData(meta, data, start, count) {
  7613. return parseArrayOrPrimitive(meta, data, start, count);
  7614. }
  7615. parseArrayData(meta, data, start, count) {
  7616. return parseArrayOrPrimitive(meta, data, start, count);
  7617. }
  7618. parseObjectData(meta, data, start, count) {
  7619. const {iScale, vScale} = meta;
  7620. const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
  7621. const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
  7622. const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
  7623. const parsed = [];
  7624. let i, ilen, item, obj;
  7625. for (i = start, ilen = start + count; i < ilen; ++i) {
  7626. obj = data[i];
  7627. item = {};
  7628. item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
  7629. parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
  7630. }
  7631. return parsed;
  7632. }
  7633. updateRangeFromParsed(range, scale, parsed, stack) {
  7634. super.updateRangeFromParsed(range, scale, parsed, stack);
  7635. const custom = parsed._custom;
  7636. if (custom && scale === this._cachedMeta.vScale) {
  7637. range.min = Math.min(range.min, custom.min);
  7638. range.max = Math.max(range.max, custom.max);
  7639. }
  7640. }
  7641. getMaxOverflow() {
  7642. return 0;
  7643. }
  7644. getLabelAndValue(index) {
  7645. const meta = this._cachedMeta;
  7646. const {iScale, vScale} = meta;
  7647. const parsed = this.getParsed(index);
  7648. const custom = parsed._custom;
  7649. const value = isFloatBar(custom)
  7650. ? '[' + custom.start + ', ' + custom.end + ']'
  7651. : '' + vScale.getLabelForValue(parsed[vScale.axis]);
  7652. return {
  7653. label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
  7654. value
  7655. };
  7656. }
  7657. initialize() {
  7658. this.enableOptionSharing = true;
  7659. super.initialize();
  7660. const meta = this._cachedMeta;
  7661. meta.stack = this.getDataset().stack;
  7662. }
  7663. update(mode) {
  7664. const meta = this._cachedMeta;
  7665. this.updateElements(meta.data, 0, meta.data.length, mode);
  7666. }
  7667. updateElements(bars, start, count, mode) {
  7668. const reset = mode === 'reset';
  7669. const {index, _cachedMeta: {vScale}} = this;
  7670. const base = vScale.getBasePixel();
  7671. const horizontal = vScale.isHorizontal();
  7672. const ruler = this._getRuler();
  7673. const firstOpts = this.resolveDataElementOptions(start, mode);
  7674. const sharedOptions = this.getSharedOptions(firstOpts);
  7675. const includeOptions = this.includeOptions(mode, sharedOptions);
  7676. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  7677. for (let i = start; i < start + count; i++) {
  7678. const parsed = this.getParsed(i);
  7679. const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : this._calculateBarValuePixels(i);
  7680. const ipixels = this._calculateBarIndexPixels(i, ruler);
  7681. const stack = (parsed._stacks || {})[vScale.axis];
  7682. const properties = {
  7683. horizontal,
  7684. base: vpixels.base,
  7685. enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom),
  7686. x: horizontal ? vpixels.head : ipixels.center,
  7687. y: horizontal ? ipixels.center : vpixels.head,
  7688. height: horizontal ? ipixels.size : Math.abs(vpixels.size),
  7689. width: horizontal ? Math.abs(vpixels.size) : ipixels.size
  7690. };
  7691. if (includeOptions) {
  7692. properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
  7693. }
  7694. const options = properties.options || bars[i].options;
  7695. setBorderSkipped(properties, options, stack, index);
  7696. setInflateAmount(properties, options, ruler.ratio);
  7697. this.updateElement(bars[i], i, properties, mode);
  7698. }
  7699. }
  7700. _getStacks(last, dataIndex) {
  7701. const meta = this._cachedMeta;
  7702. const iScale = meta.iScale;
  7703. const metasets = iScale.getMatchingVisibleMetas(this._type);
  7704. const stacked = iScale.options.stacked;
  7705. const ilen = metasets.length;
  7706. const stacks = [];
  7707. let i, item;
  7708. for (i = 0; i < ilen; ++i) {
  7709. item = metasets[i];
  7710. if (!item.controller.options.grouped) {
  7711. continue;
  7712. }
  7713. if (typeof dataIndex !== 'undefined') {
  7714. const val = item.controller.getParsed(dataIndex)[
  7715. item.controller._cachedMeta.vScale.axis
  7716. ];
  7717. if (isNullOrUndef(val) || isNaN(val)) {
  7718. continue;
  7719. }
  7720. }
  7721. if (stacked === false || stacks.indexOf(item.stack) === -1 ||
  7722. (stacked === undefined && item.stack === undefined)) {
  7723. stacks.push(item.stack);
  7724. }
  7725. if (item.index === last) {
  7726. break;
  7727. }
  7728. }
  7729. if (!stacks.length) {
  7730. stacks.push(undefined);
  7731. }
  7732. return stacks;
  7733. }
  7734. _getStackCount(index) {
  7735. return this._getStacks(undefined, index).length;
  7736. }
  7737. _getStackIndex(datasetIndex, name, dataIndex) {
  7738. const stacks = this._getStacks(datasetIndex, dataIndex);
  7739. const index = (name !== undefined)
  7740. ? stacks.indexOf(name)
  7741. : -1;
  7742. return (index === -1)
  7743. ? stacks.length - 1
  7744. : index;
  7745. }
  7746. _getRuler() {
  7747. const opts = this.options;
  7748. const meta = this._cachedMeta;
  7749. const iScale = meta.iScale;
  7750. const pixels = [];
  7751. let i, ilen;
  7752. for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
  7753. pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
  7754. }
  7755. const barThickness = opts.barThickness;
  7756. const min = barThickness || computeMinSampleSize(meta);
  7757. return {
  7758. min,
  7759. pixels,
  7760. start: iScale._startPixel,
  7761. end: iScale._endPixel,
  7762. stackCount: this._getStackCount(),
  7763. scale: iScale,
  7764. grouped: opts.grouped,
  7765. ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
  7766. };
  7767. }
  7768. _calculateBarValuePixels(index) {
  7769. const {_cachedMeta: {vScale, _stacked}, options: {base: baseValue, minBarLength}} = this;
  7770. const actualBase = baseValue || 0;
  7771. const parsed = this.getParsed(index);
  7772. const custom = parsed._custom;
  7773. const floating = isFloatBar(custom);
  7774. let value = parsed[vScale.axis];
  7775. let start = 0;
  7776. let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
  7777. let head, size;
  7778. if (length !== value) {
  7779. start = length - value;
  7780. length = value;
  7781. }
  7782. if (floating) {
  7783. value = custom.barStart;
  7784. length = custom.barEnd - custom.barStart;
  7785. if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
  7786. start = 0;
  7787. }
  7788. start += value;
  7789. }
  7790. const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
  7791. let base = vScale.getPixelForValue(startValue);
  7792. if (this.chart.getDataVisibility(index)) {
  7793. head = vScale.getPixelForValue(start + length);
  7794. } else {
  7795. head = base;
  7796. }
  7797. size = head - base;
  7798. if (Math.abs(size) < minBarLength) {
  7799. size = barSign(size, vScale, actualBase) * minBarLength;
  7800. if (value === actualBase) {
  7801. base -= size / 2;
  7802. }
  7803. const startPixel = vScale.getPixelForDecimal(0);
  7804. const endPixel = vScale.getPixelForDecimal(1);
  7805. const min = Math.min(startPixel, endPixel);
  7806. const max = Math.max(startPixel, endPixel);
  7807. base = Math.max(Math.min(base, max), min);
  7808. head = base + size;
  7809. }
  7810. if (base === vScale.getPixelForValue(actualBase)) {
  7811. const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
  7812. base += halfGrid;
  7813. size -= halfGrid;
  7814. }
  7815. return {
  7816. size,
  7817. base,
  7818. head,
  7819. center: head + size / 2
  7820. };
  7821. }
  7822. _calculateBarIndexPixels(index, ruler) {
  7823. const scale = ruler.scale;
  7824. const options = this.options;
  7825. const skipNull = options.skipNull;
  7826. const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
  7827. let center, size;
  7828. if (ruler.grouped) {
  7829. const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
  7830. const range = options.barThickness === 'flex'
  7831. ? computeFlexCategoryTraits(index, ruler, options, stackCount)
  7832. : computeFitCategoryTraits(index, ruler, options, stackCount);
  7833. const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
  7834. center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
  7835. size = Math.min(maxBarThickness, range.chunk * range.ratio);
  7836. } else {
  7837. center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
  7838. size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
  7839. }
  7840. return {
  7841. base: center - size / 2,
  7842. head: center + size / 2,
  7843. center,
  7844. size
  7845. };
  7846. }
  7847. draw() {
  7848. const meta = this._cachedMeta;
  7849. const vScale = meta.vScale;
  7850. const rects = meta.data;
  7851. const ilen = rects.length;
  7852. let i = 0;
  7853. for (; i < ilen; ++i) {
  7854. if (this.getParsed(i)[vScale.axis] !== null) {
  7855. rects[i].draw(this._ctx);
  7856. }
  7857. }
  7858. }
  7859. }
  7860. BarController.id = 'bar';
  7861. BarController.defaults = {
  7862. datasetElementType: false,
  7863. dataElementType: 'bar',
  7864. categoryPercentage: 0.8,
  7865. barPercentage: 0.9,
  7866. grouped: true,
  7867. animations: {
  7868. numbers: {
  7869. type: 'number',
  7870. properties: ['x', 'y', 'base', 'width', 'height']
  7871. }
  7872. }
  7873. };
  7874. BarController.overrides = {
  7875. scales: {
  7876. _index_: {
  7877. type: 'category',
  7878. offset: true,
  7879. grid: {
  7880. offset: true
  7881. }
  7882. },
  7883. _value_: {
  7884. type: 'linear',
  7885. beginAtZero: true,
  7886. }
  7887. }
  7888. };
  7889. class BubbleController extends DatasetController {
  7890. initialize() {
  7891. this.enableOptionSharing = true;
  7892. super.initialize();
  7893. }
  7894. parsePrimitiveData(meta, data, start, count) {
  7895. const parsed = super.parsePrimitiveData(meta, data, start, count);
  7896. for (let i = 0; i < parsed.length; i++) {
  7897. parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
  7898. }
  7899. return parsed;
  7900. }
  7901. parseArrayData(meta, data, start, count) {
  7902. const parsed = super.parseArrayData(meta, data, start, count);
  7903. for (let i = 0; i < parsed.length; i++) {
  7904. const item = data[start + i];
  7905. parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
  7906. }
  7907. return parsed;
  7908. }
  7909. parseObjectData(meta, data, start, count) {
  7910. const parsed = super.parseObjectData(meta, data, start, count);
  7911. for (let i = 0; i < parsed.length; i++) {
  7912. const item = data[start + i];
  7913. parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
  7914. }
  7915. return parsed;
  7916. }
  7917. getMaxOverflow() {
  7918. const data = this._cachedMeta.data;
  7919. let max = 0;
  7920. for (let i = data.length - 1; i >= 0; --i) {
  7921. max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
  7922. }
  7923. return max > 0 && max;
  7924. }
  7925. getLabelAndValue(index) {
  7926. const meta = this._cachedMeta;
  7927. const {xScale, yScale} = meta;
  7928. const parsed = this.getParsed(index);
  7929. const x = xScale.getLabelForValue(parsed.x);
  7930. const y = yScale.getLabelForValue(parsed.y);
  7931. const r = parsed._custom;
  7932. return {
  7933. label: meta.label,
  7934. value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
  7935. };
  7936. }
  7937. update(mode) {
  7938. const points = this._cachedMeta.data;
  7939. this.updateElements(points, 0, points.length, mode);
  7940. }
  7941. updateElements(points, start, count, mode) {
  7942. const reset = mode === 'reset';
  7943. const {iScale, vScale} = this._cachedMeta;
  7944. const firstOpts = this.resolveDataElementOptions(start, mode);
  7945. const sharedOptions = this.getSharedOptions(firstOpts);
  7946. const includeOptions = this.includeOptions(mode, sharedOptions);
  7947. const iAxis = iScale.axis;
  7948. const vAxis = vScale.axis;
  7949. for (let i = start; i < start + count; i++) {
  7950. const point = points[i];
  7951. const parsed = !reset && this.getParsed(i);
  7952. const properties = {};
  7953. const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
  7954. const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
  7955. properties.skip = isNaN(iPixel) || isNaN(vPixel);
  7956. if (includeOptions) {
  7957. properties.options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  7958. if (reset) {
  7959. properties.options.radius = 0;
  7960. }
  7961. }
  7962. this.updateElement(point, i, properties, mode);
  7963. }
  7964. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  7965. }
  7966. resolveDataElementOptions(index, mode) {
  7967. const parsed = this.getParsed(index);
  7968. let values = super.resolveDataElementOptions(index, mode);
  7969. if (values.$shared) {
  7970. values = Object.assign({}, values, {$shared: false});
  7971. }
  7972. const radius = values.radius;
  7973. if (mode !== 'active') {
  7974. values.radius = 0;
  7975. }
  7976. values.radius += valueOrDefault(parsed && parsed._custom, radius);
  7977. return values;
  7978. }
  7979. }
  7980. BubbleController.id = 'bubble';
  7981. BubbleController.defaults = {
  7982. datasetElementType: false,
  7983. dataElementType: 'point',
  7984. animations: {
  7985. numbers: {
  7986. type: 'number',
  7987. properties: ['x', 'y', 'borderWidth', 'radius']
  7988. }
  7989. }
  7990. };
  7991. BubbleController.overrides = {
  7992. scales: {
  7993. x: {
  7994. type: 'linear'
  7995. },
  7996. y: {
  7997. type: 'linear'
  7998. }
  7999. },
  8000. plugins: {
  8001. tooltip: {
  8002. callbacks: {
  8003. title() {
  8004. return '';
  8005. }
  8006. }
  8007. }
  8008. }
  8009. };
  8010. function getRatioAndOffset(rotation, circumference, cutout) {
  8011. let ratioX = 1;
  8012. let ratioY = 1;
  8013. let offsetX = 0;
  8014. let offsetY = 0;
  8015. if (circumference < TAU) {
  8016. const startAngle = rotation;
  8017. const endAngle = startAngle + circumference;
  8018. const startX = Math.cos(startAngle);
  8019. const startY = Math.sin(startAngle);
  8020. const endX = Math.cos(endAngle);
  8021. const endY = Math.sin(endAngle);
  8022. const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
  8023. const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
  8024. const maxX = calcMax(0, startX, endX);
  8025. const maxY = calcMax(HALF_PI, startY, endY);
  8026. const minX = calcMin(PI, startX, endX);
  8027. const minY = calcMin(PI + HALF_PI, startY, endY);
  8028. ratioX = (maxX - minX) / 2;
  8029. ratioY = (maxY - minY) / 2;
  8030. offsetX = -(maxX + minX) / 2;
  8031. offsetY = -(maxY + minY) / 2;
  8032. }
  8033. return {ratioX, ratioY, offsetX, offsetY};
  8034. }
  8035. class DoughnutController extends DatasetController {
  8036. constructor(chart, datasetIndex) {
  8037. super(chart, datasetIndex);
  8038. this.enableOptionSharing = true;
  8039. this.innerRadius = undefined;
  8040. this.outerRadius = undefined;
  8041. this.offsetX = undefined;
  8042. this.offsetY = undefined;
  8043. }
  8044. linkScales() {}
  8045. parse(start, count) {
  8046. const data = this.getDataset().data;
  8047. const meta = this._cachedMeta;
  8048. if (this._parsing === false) {
  8049. meta._parsed = data;
  8050. } else {
  8051. let getter = (i) => +data[i];
  8052. if (isObject(data[start])) {
  8053. const {key = 'value'} = this._parsing;
  8054. getter = (i) => +resolveObjectKey(data[i], key);
  8055. }
  8056. let i, ilen;
  8057. for (i = start, ilen = start + count; i < ilen; ++i) {
  8058. meta._parsed[i] = getter(i);
  8059. }
  8060. }
  8061. }
  8062. _getRotation() {
  8063. return toRadians(this.options.rotation - 90);
  8064. }
  8065. _getCircumference() {
  8066. return toRadians(this.options.circumference);
  8067. }
  8068. _getRotationExtents() {
  8069. let min = TAU;
  8070. let max = -TAU;
  8071. for (let i = 0; i < this.chart.data.datasets.length; ++i) {
  8072. if (this.chart.isDatasetVisible(i)) {
  8073. const controller = this.chart.getDatasetMeta(i).controller;
  8074. const rotation = controller._getRotation();
  8075. const circumference = controller._getCircumference();
  8076. min = Math.min(min, rotation);
  8077. max = Math.max(max, rotation + circumference);
  8078. }
  8079. }
  8080. return {
  8081. rotation: min,
  8082. circumference: max - min,
  8083. };
  8084. }
  8085. update(mode) {
  8086. const chart = this.chart;
  8087. const {chartArea} = chart;
  8088. const meta = this._cachedMeta;
  8089. const arcs = meta.data;
  8090. const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
  8091. const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
  8092. const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
  8093. const chartWeight = this._getRingWeight(this.index);
  8094. const {circumference, rotation} = this._getRotationExtents();
  8095. const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);
  8096. const maxWidth = (chartArea.width - spacing) / ratioX;
  8097. const maxHeight = (chartArea.height - spacing) / ratioY;
  8098. const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
  8099. const outerRadius = toDimension(this.options.radius, maxRadius);
  8100. const innerRadius = Math.max(outerRadius * cutout, 0);
  8101. const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
  8102. this.offsetX = offsetX * outerRadius;
  8103. this.offsetY = offsetY * outerRadius;
  8104. meta.total = this.calculateTotal();
  8105. this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
  8106. this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
  8107. this.updateElements(arcs, 0, arcs.length, mode);
  8108. }
  8109. _circumference(i, reset) {
  8110. const opts = this.options;
  8111. const meta = this._cachedMeta;
  8112. const circumference = this._getCircumference();
  8113. if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
  8114. return 0;
  8115. }
  8116. return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
  8117. }
  8118. updateElements(arcs, start, count, mode) {
  8119. const reset = mode === 'reset';
  8120. const chart = this.chart;
  8121. const chartArea = chart.chartArea;
  8122. const opts = chart.options;
  8123. const animationOpts = opts.animation;
  8124. const centerX = (chartArea.left + chartArea.right) / 2;
  8125. const centerY = (chartArea.top + chartArea.bottom) / 2;
  8126. const animateScale = reset && animationOpts.animateScale;
  8127. const innerRadius = animateScale ? 0 : this.innerRadius;
  8128. const outerRadius = animateScale ? 0 : this.outerRadius;
  8129. const firstOpts = this.resolveDataElementOptions(start, mode);
  8130. const sharedOptions = this.getSharedOptions(firstOpts);
  8131. const includeOptions = this.includeOptions(mode, sharedOptions);
  8132. let startAngle = this._getRotation();
  8133. let i;
  8134. for (i = 0; i < start; ++i) {
  8135. startAngle += this._circumference(i, reset);
  8136. }
  8137. for (i = start; i < start + count; ++i) {
  8138. const circumference = this._circumference(i, reset);
  8139. const arc = arcs[i];
  8140. const properties = {
  8141. x: centerX + this.offsetX,
  8142. y: centerY + this.offsetY,
  8143. startAngle,
  8144. endAngle: startAngle + circumference,
  8145. circumference,
  8146. outerRadius,
  8147. innerRadius
  8148. };
  8149. if (includeOptions) {
  8150. properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
  8151. }
  8152. startAngle += circumference;
  8153. this.updateElement(arc, i, properties, mode);
  8154. }
  8155. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  8156. }
  8157. calculateTotal() {
  8158. const meta = this._cachedMeta;
  8159. const metaData = meta.data;
  8160. let total = 0;
  8161. let i;
  8162. for (i = 0; i < metaData.length; i++) {
  8163. const value = meta._parsed[i];
  8164. if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
  8165. total += Math.abs(value);
  8166. }
  8167. }
  8168. return total;
  8169. }
  8170. calculateCircumference(value) {
  8171. const total = this._cachedMeta.total;
  8172. if (total > 0 && !isNaN(value)) {
  8173. return TAU * (Math.abs(value) / total);
  8174. }
  8175. return 0;
  8176. }
  8177. getLabelAndValue(index) {
  8178. const meta = this._cachedMeta;
  8179. const chart = this.chart;
  8180. const labels = chart.data.labels || [];
  8181. const value = formatNumber(meta._parsed[index], chart.options.locale);
  8182. return {
  8183. label: labels[index] || '',
  8184. value,
  8185. };
  8186. }
  8187. getMaxBorderWidth(arcs) {
  8188. let max = 0;
  8189. const chart = this.chart;
  8190. let i, ilen, meta, controller, options;
  8191. if (!arcs) {
  8192. for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
  8193. if (chart.isDatasetVisible(i)) {
  8194. meta = chart.getDatasetMeta(i);
  8195. arcs = meta.data;
  8196. controller = meta.controller;
  8197. break;
  8198. }
  8199. }
  8200. }
  8201. if (!arcs) {
  8202. return 0;
  8203. }
  8204. for (i = 0, ilen = arcs.length; i < ilen; ++i) {
  8205. options = controller.resolveDataElementOptions(i);
  8206. if (options.borderAlign !== 'inner') {
  8207. max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
  8208. }
  8209. }
  8210. return max;
  8211. }
  8212. getMaxOffset(arcs) {
  8213. let max = 0;
  8214. for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
  8215. const options = this.resolveDataElementOptions(i);
  8216. max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
  8217. }
  8218. return max;
  8219. }
  8220. _getRingWeightOffset(datasetIndex) {
  8221. let ringWeightOffset = 0;
  8222. for (let i = 0; i < datasetIndex; ++i) {
  8223. if (this.chart.isDatasetVisible(i)) {
  8224. ringWeightOffset += this._getRingWeight(i);
  8225. }
  8226. }
  8227. return ringWeightOffset;
  8228. }
  8229. _getRingWeight(datasetIndex) {
  8230. return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
  8231. }
  8232. _getVisibleDatasetWeightTotal() {
  8233. return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
  8234. }
  8235. }
  8236. DoughnutController.id = 'doughnut';
  8237. DoughnutController.defaults = {
  8238. datasetElementType: false,
  8239. dataElementType: 'arc',
  8240. animation: {
  8241. animateRotate: true,
  8242. animateScale: false
  8243. },
  8244. animations: {
  8245. numbers: {
  8246. type: 'number',
  8247. properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing']
  8248. },
  8249. },
  8250. cutout: '50%',
  8251. rotation: 0,
  8252. circumference: 360,
  8253. radius: '100%',
  8254. spacing: 0,
  8255. indexAxis: 'r',
  8256. };
  8257. DoughnutController.descriptors = {
  8258. _scriptable: (name) => name !== 'spacing',
  8259. _indexable: (name) => name !== 'spacing',
  8260. };
  8261. DoughnutController.overrides = {
  8262. aspectRatio: 1,
  8263. plugins: {
  8264. legend: {
  8265. labels: {
  8266. generateLabels(chart) {
  8267. const data = chart.data;
  8268. if (data.labels.length && data.datasets.length) {
  8269. const {labels: {pointStyle}} = chart.legend.options;
  8270. return data.labels.map((label, i) => {
  8271. const meta = chart.getDatasetMeta(0);
  8272. const style = meta.controller.getStyle(i);
  8273. return {
  8274. text: label,
  8275. fillStyle: style.backgroundColor,
  8276. strokeStyle: style.borderColor,
  8277. lineWidth: style.borderWidth,
  8278. pointStyle: pointStyle,
  8279. hidden: !chart.getDataVisibility(i),
  8280. index: i
  8281. };
  8282. });
  8283. }
  8284. return [];
  8285. }
  8286. },
  8287. onClick(e, legendItem, legend) {
  8288. legend.chart.toggleDataVisibility(legendItem.index);
  8289. legend.chart.update();
  8290. }
  8291. },
  8292. tooltip: {
  8293. callbacks: {
  8294. title() {
  8295. return '';
  8296. },
  8297. label(tooltipItem) {
  8298. let dataLabel = tooltipItem.label;
  8299. const value = ': ' + tooltipItem.formattedValue;
  8300. if (isArray(dataLabel)) {
  8301. dataLabel = dataLabel.slice();
  8302. dataLabel[0] += value;
  8303. } else {
  8304. dataLabel += value;
  8305. }
  8306. return dataLabel;
  8307. }
  8308. }
  8309. }
  8310. }
  8311. };
  8312. class LineController extends DatasetController {
  8313. initialize() {
  8314. this.enableOptionSharing = true;
  8315. this.supportsDecimation = true;
  8316. super.initialize();
  8317. }
  8318. update(mode) {
  8319. const meta = this._cachedMeta;
  8320. const {dataset: line, data: points = [], _dataset} = meta;
  8321. const animationsDisabled = this.chart._animationsDisabled;
  8322. let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
  8323. this._drawStart = start;
  8324. this._drawCount = count;
  8325. if (scaleRangesChanged(meta)) {
  8326. start = 0;
  8327. count = points.length;
  8328. }
  8329. line._chart = this.chart;
  8330. line._datasetIndex = this.index;
  8331. line._decimated = !!_dataset._decimated;
  8332. line.points = points;
  8333. const options = this.resolveDatasetElementOptions(mode);
  8334. if (!this.options.showLine) {
  8335. options.borderWidth = 0;
  8336. }
  8337. options.segment = this.options.segment;
  8338. this.updateElement(line, undefined, {
  8339. animated: !animationsDisabled,
  8340. options
  8341. }, mode);
  8342. this.updateElements(points, start, count, mode);
  8343. }
  8344. updateElements(points, start, count, mode) {
  8345. const reset = mode === 'reset';
  8346. const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;
  8347. const firstOpts = this.resolveDataElementOptions(start, mode);
  8348. const sharedOptions = this.getSharedOptions(firstOpts);
  8349. const includeOptions = this.includeOptions(mode, sharedOptions);
  8350. const iAxis = iScale.axis;
  8351. const vAxis = vScale.axis;
  8352. const {spanGaps, segment} = this.options;
  8353. const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
  8354. const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
  8355. let prevParsed = start > 0 && this.getParsed(start - 1);
  8356. for (let i = start; i < start + count; ++i) {
  8357. const point = points[i];
  8358. const parsed = this.getParsed(i);
  8359. const properties = directUpdate ? point : {};
  8360. const nullData = isNullOrUndef(parsed[vAxis]);
  8361. const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
  8362. const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
  8363. properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
  8364. properties.stop = i > 0 && (parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
  8365. if (segment) {
  8366. properties.parsed = parsed;
  8367. properties.raw = _dataset.data[i];
  8368. }
  8369. if (includeOptions) {
  8370. properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  8371. }
  8372. if (!directUpdate) {
  8373. this.updateElement(point, i, properties, mode);
  8374. }
  8375. prevParsed = parsed;
  8376. }
  8377. this.updateSharedOptions(sharedOptions, mode, firstOpts);
  8378. }
  8379. getMaxOverflow() {
  8380. const meta = this._cachedMeta;
  8381. const dataset = meta.dataset;
  8382. const border = dataset.options && dataset.options.borderWidth || 0;
  8383. const data = meta.data || [];
  8384. if (!data.length) {
  8385. return border;
  8386. }
  8387. const firstPoint = data[0].size(this.resolveDataElementOptions(0));
  8388. const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
  8389. return Math.max(border, firstPoint, lastPoint) / 2;
  8390. }
  8391. draw() {
  8392. const meta = this._cachedMeta;
  8393. meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
  8394. super.draw();
  8395. }
  8396. }
  8397. LineController.id = 'line';
  8398. LineController.defaults = {
  8399. datasetElementType: 'line',
  8400. dataElementType: 'point',
  8401. showLine: true,
  8402. spanGaps: false,
  8403. };
  8404. LineController.overrides = {
  8405. scales: {
  8406. _index_: {
  8407. type: 'category',
  8408. },
  8409. _value_: {
  8410. type: 'linear',
  8411. },
  8412. }
  8413. };
  8414. function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
  8415. const pointCount = points.length;
  8416. let start = 0;
  8417. let count = pointCount;
  8418. if (meta._sorted) {
  8419. const {iScale, _parsed} = meta;
  8420. const axis = iScale.axis;
  8421. const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
  8422. if (minDefined) {
  8423. start = _limitValue(Math.min(
  8424. _lookupByKey(_parsed, iScale.axis, min).lo,
  8425. animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
  8426. 0, pointCount - 1);
  8427. }
  8428. if (maxDefined) {
  8429. count = _limitValue(Math.max(
  8430. _lookupByKey(_parsed, iScale.axis, max).hi + 1,
  8431. animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1),
  8432. start, pointCount) - start;
  8433. } else {
  8434. count = pointCount - start;
  8435. }
  8436. }
  8437. return {start, count};
  8438. }
  8439. function scaleRangesChanged(meta) {
  8440. const {xScale, yScale, _scaleRanges} = meta;
  8441. const newRanges = {
  8442. xmin: xScale.min,
  8443. xmax: xScale.max,
  8444. ymin: yScale.min,
  8445. ymax: yScale.max
  8446. };
  8447. if (!_scaleRanges) {
  8448. meta._scaleRanges = newRanges;
  8449. return true;
  8450. }
  8451. const changed = _scaleRanges.xmin !== xScale.min
  8452. || _scaleRanges.xmax !== xScale.max
  8453. || _scaleRanges.ymin !== yScale.min
  8454. || _scaleRanges.ymax !== yScale.max;
  8455. Object.assign(_scaleRanges, newRanges);
  8456. return changed;
  8457. }
  8458. class PolarAreaController extends DatasetController {
  8459. constructor(chart, datasetIndex) {
  8460. super(chart, datasetIndex);
  8461. this.innerRadius = undefined;
  8462. this.outerRadius = undefined;
  8463. }
  8464. getLabelAndValue(index) {
  8465. const meta = this._cachedMeta;
  8466. const chart = this.chart;
  8467. const labels = chart.data.labels || [];
  8468. const value = formatNumber(meta._parsed[index].r, chart.options.locale);
  8469. return {
  8470. label: labels[index] || '',
  8471. value,
  8472. };
  8473. }
  8474. parseObjectData(meta, data, start, count) {
  8475. return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  8476. }
  8477. update(mode) {
  8478. const arcs = this._cachedMeta.data;
  8479. this._updateRadius();
  8480. this.updateElements(arcs, 0, arcs.length, mode);
  8481. }
  8482. _updateRadius() {
  8483. const chart = this.chart;
  8484. const chartArea = chart.chartArea;
  8485. const opts = chart.options;
  8486. const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
  8487. const outerRadius = Math.max(minSize / 2, 0);
  8488. const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
  8489. const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
  8490. this.outerRadius = outerRadius - (radiusLength * this.index);
  8491. this.innerRadius = this.outerRadius - radiusLength;
  8492. }
  8493. updateElements(arcs, start, count, mode) {
  8494. const reset = mode === 'reset';
  8495. const chart = this.chart;
  8496. const opts = chart.options;
  8497. const animationOpts = opts.animation;
  8498. const scale = this._cachedMeta.rScale;
  8499. const centerX = scale.xCenter;
  8500. const centerY = scale.yCenter;
  8501. const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
  8502. let angle = datasetStartAngle;
  8503. let i;
  8504. const defaultAngle = 360 / this.countVisibleElements();
  8505. for (i = 0; i < start; ++i) {
  8506. angle += this._computeAngle(i, mode, defaultAngle);
  8507. }
  8508. for (i = start; i < start + count; i++) {
  8509. const arc = arcs[i];
  8510. let startAngle = angle;
  8511. let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
  8512. let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
  8513. angle = endAngle;
  8514. if (reset) {
  8515. if (animationOpts.animateScale) {
  8516. outerRadius = 0;
  8517. }
  8518. if (animationOpts.animateRotate) {
  8519. startAngle = endAngle = datasetStartAngle;
  8520. }
  8521. }
  8522. const properties = {
  8523. x: centerX,
  8524. y: centerY,
  8525. innerRadius: 0,
  8526. outerRadius,
  8527. startAngle,
  8528. endAngle,
  8529. options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
  8530. };
  8531. this.updateElement(arc, i, properties, mode);
  8532. }
  8533. }
  8534. countVisibleElements() {
  8535. const meta = this._cachedMeta;
  8536. let count = 0;
  8537. meta.data.forEach((element, index) => {
  8538. if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
  8539. count++;
  8540. }
  8541. });
  8542. return count;
  8543. }
  8544. _computeAngle(index, mode, defaultAngle) {
  8545. return this.chart.getDataVisibility(index)
  8546. ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle)
  8547. : 0;
  8548. }
  8549. }
  8550. PolarAreaController.id = 'polarArea';
  8551. PolarAreaController.defaults = {
  8552. dataElementType: 'arc',
  8553. animation: {
  8554. animateRotate: true,
  8555. animateScale: true
  8556. },
  8557. animations: {
  8558. numbers: {
  8559. type: 'number',
  8560. properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
  8561. },
  8562. },
  8563. indexAxis: 'r',
  8564. startAngle: 0,
  8565. };
  8566. PolarAreaController.overrides = {
  8567. aspectRatio: 1,
  8568. plugins: {
  8569. legend: {
  8570. labels: {
  8571. generateLabels(chart) {
  8572. const data = chart.data;
  8573. if (data.labels.length && data.datasets.length) {
  8574. const {labels: {pointStyle}} = chart.legend.options;
  8575. return data.labels.map((label, i) => {
  8576. const meta = chart.getDatasetMeta(0);
  8577. const style = meta.controller.getStyle(i);
  8578. return {
  8579. text: label,
  8580. fillStyle: style.backgroundColor,
  8581. strokeStyle: style.borderColor,
  8582. lineWidth: style.borderWidth,
  8583. pointStyle: pointStyle,
  8584. hidden: !chart.getDataVisibility(i),
  8585. index: i
  8586. };
  8587. });
  8588. }
  8589. return [];
  8590. }
  8591. },
  8592. onClick(e, legendItem, legend) {
  8593. legend.chart.toggleDataVisibility(legendItem.index);
  8594. legend.chart.update();
  8595. }
  8596. },
  8597. tooltip: {
  8598. callbacks: {
  8599. title() {
  8600. return '';
  8601. },
  8602. label(context) {
  8603. return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue;
  8604. }
  8605. }
  8606. }
  8607. },
  8608. scales: {
  8609. r: {
  8610. type: 'radialLinear',
  8611. angleLines: {
  8612. display: false
  8613. },
  8614. beginAtZero: true,
  8615. grid: {
  8616. circular: true
  8617. },
  8618. pointLabels: {
  8619. display: false
  8620. },
  8621. startAngle: 0
  8622. }
  8623. }
  8624. };
  8625. class PieController extends DoughnutController {
  8626. }
  8627. PieController.id = 'pie';
  8628. PieController.defaults = {
  8629. cutout: 0,
  8630. rotation: 0,
  8631. circumference: 360,
  8632. radius: '100%'
  8633. };
  8634. class RadarController extends DatasetController {
  8635. getLabelAndValue(index) {
  8636. const vScale = this._cachedMeta.vScale;
  8637. const parsed = this.getParsed(index);
  8638. return {
  8639. label: vScale.getLabels()[index],
  8640. value: '' + vScale.getLabelForValue(parsed[vScale.axis])
  8641. };
  8642. }
  8643. parseObjectData(meta, data, start, count) {
  8644. return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
  8645. }
  8646. update(mode) {
  8647. const meta = this._cachedMeta;
  8648. const line = meta.dataset;
  8649. const points = meta.data || [];
  8650. const labels = meta.iScale.getLabels();
  8651. line.points = points;
  8652. if (mode !== 'resize') {
  8653. const options = this.resolveDatasetElementOptions(mode);
  8654. if (!this.options.showLine) {
  8655. options.borderWidth = 0;
  8656. }
  8657. const properties = {
  8658. _loop: true,
  8659. _fullLoop: labels.length === points.length,
  8660. options
  8661. };
  8662. this.updateElement(line, undefined, properties, mode);
  8663. }
  8664. this.updateElements(points, 0, points.length, mode);
  8665. }
  8666. updateElements(points, start, count, mode) {
  8667. const scale = this._cachedMeta.rScale;
  8668. const reset = mode === 'reset';
  8669. for (let i = start; i < start + count; i++) {
  8670. const point = points[i];
  8671. const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
  8672. const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
  8673. const x = reset ? scale.xCenter : pointPosition.x;
  8674. const y = reset ? scale.yCenter : pointPosition.y;
  8675. const properties = {
  8676. x,
  8677. y,
  8678. angle: pointPosition.angle,
  8679. skip: isNaN(x) || isNaN(y),
  8680. options
  8681. };
  8682. this.updateElement(point, i, properties, mode);
  8683. }
  8684. }
  8685. }
  8686. RadarController.id = 'radar';
  8687. RadarController.defaults = {
  8688. datasetElementType: 'line',
  8689. dataElementType: 'point',
  8690. indexAxis: 'r',
  8691. showLine: true,
  8692. elements: {
  8693. line: {
  8694. fill: 'start'
  8695. }
  8696. },
  8697. };
  8698. RadarController.overrides = {
  8699. aspectRatio: 1,
  8700. scales: {
  8701. r: {
  8702. type: 'radialLinear',
  8703. }
  8704. }
  8705. };
  8706. class ScatterController extends LineController {
  8707. }
  8708. ScatterController.id = 'scatter';
  8709. ScatterController.defaults = {
  8710. showLine: false,
  8711. fill: false
  8712. };
  8713. ScatterController.overrides = {
  8714. interaction: {
  8715. mode: 'point'
  8716. },
  8717. plugins: {
  8718. tooltip: {
  8719. callbacks: {
  8720. title() {
  8721. return '';
  8722. },
  8723. label(item) {
  8724. return '(' + item.label + ', ' + item.formattedValue + ')';
  8725. }
  8726. }
  8727. }
  8728. },
  8729. scales: {
  8730. x: {
  8731. type: 'linear'
  8732. },
  8733. y: {
  8734. type: 'linear'
  8735. }
  8736. }
  8737. };
  8738. var controllers = /*#__PURE__*/Object.freeze({
  8739. __proto__: null,
  8740. BarController: BarController,
  8741. BubbleController: BubbleController,
  8742. DoughnutController: DoughnutController,
  8743. LineController: LineController,
  8744. PolarAreaController: PolarAreaController,
  8745. PieController: PieController,
  8746. RadarController: RadarController,
  8747. ScatterController: ScatterController
  8748. });
  8749. function clipArc(ctx, element, endAngle) {
  8750. const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
  8751. let angleMargin = pixelMargin / outerRadius;
  8752. ctx.beginPath();
  8753. ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
  8754. if (innerRadius > pixelMargin) {
  8755. angleMargin = pixelMargin / innerRadius;
  8756. ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
  8757. } else {
  8758. ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
  8759. }
  8760. ctx.closePath();
  8761. ctx.clip();
  8762. }
  8763. function toRadiusCorners(value) {
  8764. return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
  8765. }
  8766. function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
  8767. const o = toRadiusCorners(arc.options.borderRadius);
  8768. const halfThickness = (outerRadius - innerRadius) / 2;
  8769. const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
  8770. const computeOuterLimit = (val) => {
  8771. const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
  8772. return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
  8773. };
  8774. return {
  8775. outerStart: computeOuterLimit(o.outerStart),
  8776. outerEnd: computeOuterLimit(o.outerEnd),
  8777. innerStart: _limitValue(o.innerStart, 0, innerLimit),
  8778. innerEnd: _limitValue(o.innerEnd, 0, innerLimit),
  8779. };
  8780. }
  8781. function rThetaToXY(r, theta, x, y) {
  8782. return {
  8783. x: x + r * Math.cos(theta),
  8784. y: y + r * Math.sin(theta),
  8785. };
  8786. }
  8787. function pathArc(ctx, element, offset, spacing, end) {
  8788. const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;
  8789. const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
  8790. const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
  8791. let spacingOffset = 0;
  8792. const alpha = end - start;
  8793. if (spacing) {
  8794. const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
  8795. const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
  8796. const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
  8797. const adjustedAngle = avNogSpacingRadius !== 0 ? (alpha * avNogSpacingRadius) / (avNogSpacingRadius + spacing) : alpha;
  8798. spacingOffset = (alpha - adjustedAngle) / 2;
  8799. }
  8800. const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
  8801. const angleOffset = (alpha - beta) / 2;
  8802. const startAngle = start + angleOffset + spacingOffset;
  8803. const endAngle = end - angleOffset - spacingOffset;
  8804. const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
  8805. const outerStartAdjustedRadius = outerRadius - outerStart;
  8806. const outerEndAdjustedRadius = outerRadius - outerEnd;
  8807. const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
  8808. const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
  8809. const innerStartAdjustedRadius = innerRadius + innerStart;
  8810. const innerEndAdjustedRadius = innerRadius + innerEnd;
  8811. const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
  8812. const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
  8813. ctx.beginPath();
  8814. ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
  8815. if (outerEnd > 0) {
  8816. const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
  8817. ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
  8818. }
  8819. const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
  8820. ctx.lineTo(p4.x, p4.y);
  8821. if (innerEnd > 0) {
  8822. const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
  8823. ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
  8824. }
  8825. ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);
  8826. if (innerStart > 0) {
  8827. const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
  8828. ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
  8829. }
  8830. const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
  8831. ctx.lineTo(p8.x, p8.y);
  8832. if (outerStart > 0) {
  8833. const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
  8834. ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
  8835. }
  8836. ctx.closePath();
  8837. }
  8838. function drawArc(ctx, element, offset, spacing) {
  8839. const {fullCircles, startAngle, circumference} = element;
  8840. let endAngle = element.endAngle;
  8841. if (fullCircles) {
  8842. pathArc(ctx, element, offset, spacing, startAngle + TAU);
  8843. for (let i = 0; i < fullCircles; ++i) {
  8844. ctx.fill();
  8845. }
  8846. if (!isNaN(circumference)) {
  8847. endAngle = startAngle + circumference % TAU;
  8848. if (circumference % TAU === 0) {
  8849. endAngle += TAU;
  8850. }
  8851. }
  8852. }
  8853. pathArc(ctx, element, offset, spacing, endAngle);
  8854. ctx.fill();
  8855. return endAngle;
  8856. }
  8857. function drawFullCircleBorders(ctx, element, inner) {
  8858. const {x, y, startAngle, pixelMargin, fullCircles} = element;
  8859. const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
  8860. const innerRadius = element.innerRadius + pixelMargin;
  8861. let i;
  8862. if (inner) {
  8863. clipArc(ctx, element, startAngle + TAU);
  8864. }
  8865. ctx.beginPath();
  8866. ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
  8867. for (i = 0; i < fullCircles; ++i) {
  8868. ctx.stroke();
  8869. }
  8870. ctx.beginPath();
  8871. ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
  8872. for (i = 0; i < fullCircles; ++i) {
  8873. ctx.stroke();
  8874. }
  8875. }
  8876. function drawBorder(ctx, element, offset, spacing, endAngle) {
  8877. const {options} = element;
  8878. const {borderWidth, borderJoinStyle} = options;
  8879. const inner = options.borderAlign === 'inner';
  8880. if (!borderWidth) {
  8881. return;
  8882. }
  8883. if (inner) {
  8884. ctx.lineWidth = borderWidth * 2;
  8885. ctx.lineJoin = borderJoinStyle || 'round';
  8886. } else {
  8887. ctx.lineWidth = borderWidth;
  8888. ctx.lineJoin = borderJoinStyle || 'bevel';
  8889. }
  8890. if (element.fullCircles) {
  8891. drawFullCircleBorders(ctx, element, inner);
  8892. }
  8893. if (inner) {
  8894. clipArc(ctx, element, endAngle);
  8895. }
  8896. pathArc(ctx, element, offset, spacing, endAngle);
  8897. ctx.stroke();
  8898. }
  8899. class ArcElement extends Element {
  8900. constructor(cfg) {
  8901. super();
  8902. this.options = undefined;
  8903. this.circumference = undefined;
  8904. this.startAngle = undefined;
  8905. this.endAngle = undefined;
  8906. this.innerRadius = undefined;
  8907. this.outerRadius = undefined;
  8908. this.pixelMargin = 0;
  8909. this.fullCircles = 0;
  8910. if (cfg) {
  8911. Object.assign(this, cfg);
  8912. }
  8913. }
  8914. inRange(chartX, chartY, useFinalPosition) {
  8915. const point = this.getProps(['x', 'y'], useFinalPosition);
  8916. const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});
  8917. const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
  8918. 'startAngle',
  8919. 'endAngle',
  8920. 'innerRadius',
  8921. 'outerRadius',
  8922. 'circumference'
  8923. ], useFinalPosition);
  8924. const rAdjust = this.options.spacing / 2;
  8925. const _circumference = valueOrDefault(circumference, endAngle - startAngle);
  8926. const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
  8927. const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
  8928. return (betweenAngles && withinRadius);
  8929. }
  8930. getCenterPoint(useFinalPosition) {
  8931. const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
  8932. 'x',
  8933. 'y',
  8934. 'startAngle',
  8935. 'endAngle',
  8936. 'innerRadius',
  8937. 'outerRadius',
  8938. 'circumference',
  8939. ], useFinalPosition);
  8940. const {offset, spacing} = this.options;
  8941. const halfAngle = (startAngle + endAngle) / 2;
  8942. const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
  8943. return {
  8944. x: x + Math.cos(halfAngle) * halfRadius,
  8945. y: y + Math.sin(halfAngle) * halfRadius
  8946. };
  8947. }
  8948. tooltipPosition(useFinalPosition) {
  8949. return this.getCenterPoint(useFinalPosition);
  8950. }
  8951. draw(ctx) {
  8952. const {options, circumference} = this;
  8953. const offset = (options.offset || 0) / 2;
  8954. const spacing = (options.spacing || 0) / 2;
  8955. this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
  8956. this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
  8957. if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
  8958. return;
  8959. }
  8960. ctx.save();
  8961. let radiusOffset = 0;
  8962. if (offset) {
  8963. radiusOffset = offset / 2;
  8964. const halfAngle = (this.startAngle + this.endAngle) / 2;
  8965. ctx.translate(Math.cos(halfAngle) * radiusOffset, Math.sin(halfAngle) * radiusOffset);
  8966. if (this.circumference >= PI) {
  8967. radiusOffset = offset;
  8968. }
  8969. }
  8970. ctx.fillStyle = options.backgroundColor;
  8971. ctx.strokeStyle = options.borderColor;
  8972. const endAngle = drawArc(ctx, this, radiusOffset, spacing);
  8973. drawBorder(ctx, this, radiusOffset, spacing, endAngle);
  8974. ctx.restore();
  8975. }
  8976. }
  8977. ArcElement.id = 'arc';
  8978. ArcElement.defaults = {
  8979. borderAlign: 'center',
  8980. borderColor: '#fff',
  8981. borderJoinStyle: undefined,
  8982. borderRadius: 0,
  8983. borderWidth: 2,
  8984. offset: 0,
  8985. spacing: 0,
  8986. angle: undefined,
  8987. };
  8988. ArcElement.defaultRoutes = {
  8989. backgroundColor: 'backgroundColor'
  8990. };
  8991. function setStyle(ctx, options, style = options) {
  8992. ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
  8993. ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
  8994. ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
  8995. ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
  8996. ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
  8997. ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
  8998. }
  8999. function lineTo(ctx, previous, target) {
  9000. ctx.lineTo(target.x, target.y);
  9001. }
  9002. function getLineMethod(options) {
  9003. if (options.stepped) {
  9004. return _steppedLineTo;
  9005. }
  9006. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  9007. return _bezierCurveTo;
  9008. }
  9009. return lineTo;
  9010. }
  9011. function pathVars(points, segment, params = {}) {
  9012. const count = points.length;
  9013. const {start: paramsStart = 0, end: paramsEnd = count - 1} = params;
  9014. const {start: segmentStart, end: segmentEnd} = segment;
  9015. const start = Math.max(paramsStart, segmentStart);
  9016. const end = Math.min(paramsEnd, segmentEnd);
  9017. const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
  9018. return {
  9019. count,
  9020. start,
  9021. loop: segment.loop,
  9022. ilen: end < start && !outside ? count + end - start : end - start
  9023. };
  9024. }
  9025. function pathSegment(ctx, line, segment, params) {
  9026. const {points, options} = line;
  9027. const {count, start, loop, ilen} = pathVars(points, segment, params);
  9028. const lineMethod = getLineMethod(options);
  9029. let {move = true, reverse} = params || {};
  9030. let i, point, prev;
  9031. for (i = 0; i <= ilen; ++i) {
  9032. point = points[(start + (reverse ? ilen - i : i)) % count];
  9033. if (point.skip) {
  9034. continue;
  9035. } else if (move) {
  9036. ctx.moveTo(point.x, point.y);
  9037. move = false;
  9038. } else {
  9039. lineMethod(ctx, prev, point, reverse, options.stepped);
  9040. }
  9041. prev = point;
  9042. }
  9043. if (loop) {
  9044. point = points[(start + (reverse ? ilen : 0)) % count];
  9045. lineMethod(ctx, prev, point, reverse, options.stepped);
  9046. }
  9047. return !!loop;
  9048. }
  9049. function fastPathSegment(ctx, line, segment, params) {
  9050. const points = line.points;
  9051. const {count, start, ilen} = pathVars(points, segment, params);
  9052. const {move = true, reverse} = params || {};
  9053. let avgX = 0;
  9054. let countX = 0;
  9055. let i, point, prevX, minY, maxY, lastY;
  9056. const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count;
  9057. const drawX = () => {
  9058. if (minY !== maxY) {
  9059. ctx.lineTo(avgX, maxY);
  9060. ctx.lineTo(avgX, minY);
  9061. ctx.lineTo(avgX, lastY);
  9062. }
  9063. };
  9064. if (move) {
  9065. point = points[pointIndex(0)];
  9066. ctx.moveTo(point.x, point.y);
  9067. }
  9068. for (i = 0; i <= ilen; ++i) {
  9069. point = points[pointIndex(i)];
  9070. if (point.skip) {
  9071. continue;
  9072. }
  9073. const x = point.x;
  9074. const y = point.y;
  9075. const truncX = x | 0;
  9076. if (truncX === prevX) {
  9077. if (y < minY) {
  9078. minY = y;
  9079. } else if (y > maxY) {
  9080. maxY = y;
  9081. }
  9082. avgX = (countX * avgX + x) / ++countX;
  9083. } else {
  9084. drawX();
  9085. ctx.lineTo(x, y);
  9086. prevX = truncX;
  9087. countX = 0;
  9088. minY = maxY = y;
  9089. }
  9090. lastY = y;
  9091. }
  9092. drawX();
  9093. }
  9094. function _getSegmentMethod(line) {
  9095. const opts = line.options;
  9096. const borderDash = opts.borderDash && opts.borderDash.length;
  9097. const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
  9098. return useFastPath ? fastPathSegment : pathSegment;
  9099. }
  9100. function _getInterpolationMethod(options) {
  9101. if (options.stepped) {
  9102. return _steppedInterpolation;
  9103. }
  9104. if (options.tension || options.cubicInterpolationMode === 'monotone') {
  9105. return _bezierInterpolation;
  9106. }
  9107. return _pointInLine;
  9108. }
  9109. function strokePathWithCache(ctx, line, start, count) {
  9110. let path = line._path;
  9111. if (!path) {
  9112. path = line._path = new Path2D();
  9113. if (line.path(path, start, count)) {
  9114. path.closePath();
  9115. }
  9116. }
  9117. setStyle(ctx, line.options);
  9118. ctx.stroke(path);
  9119. }
  9120. function strokePathDirect(ctx, line, start, count) {
  9121. const {segments, options} = line;
  9122. const segmentMethod = _getSegmentMethod(line);
  9123. for (const segment of segments) {
  9124. setStyle(ctx, options, segment.style);
  9125. ctx.beginPath();
  9126. if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) {
  9127. ctx.closePath();
  9128. }
  9129. ctx.stroke();
  9130. }
  9131. }
  9132. const usePath2D = typeof Path2D === 'function';
  9133. function draw(ctx, line, start, count) {
  9134. if (usePath2D && !line.options.segment) {
  9135. strokePathWithCache(ctx, line, start, count);
  9136. } else {
  9137. strokePathDirect(ctx, line, start, count);
  9138. }
  9139. }
  9140. class LineElement extends Element {
  9141. constructor(cfg) {
  9142. super();
  9143. this.animated = true;
  9144. this.options = undefined;
  9145. this._chart = undefined;
  9146. this._loop = undefined;
  9147. this._fullLoop = undefined;
  9148. this._path = undefined;
  9149. this._points = undefined;
  9150. this._segments = undefined;
  9151. this._decimated = false;
  9152. this._pointsUpdated = false;
  9153. this._datasetIndex = undefined;
  9154. if (cfg) {
  9155. Object.assign(this, cfg);
  9156. }
  9157. }
  9158. updateControlPoints(chartArea, indexAxis) {
  9159. const options = this.options;
  9160. if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
  9161. const loop = options.spanGaps ? this._loop : this._fullLoop;
  9162. _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
  9163. this._pointsUpdated = true;
  9164. }
  9165. }
  9166. set points(points) {
  9167. this._points = points;
  9168. delete this._segments;
  9169. delete this._path;
  9170. this._pointsUpdated = false;
  9171. }
  9172. get points() {
  9173. return this._points;
  9174. }
  9175. get segments() {
  9176. return this._segments || (this._segments = _computeSegments(this, this.options.segment));
  9177. }
  9178. first() {
  9179. const segments = this.segments;
  9180. const points = this.points;
  9181. return segments.length && points[segments[0].start];
  9182. }
  9183. last() {
  9184. const segments = this.segments;
  9185. const points = this.points;
  9186. const count = segments.length;
  9187. return count && points[segments[count - 1].end];
  9188. }
  9189. interpolate(point, property) {
  9190. const options = this.options;
  9191. const value = point[property];
  9192. const points = this.points;
  9193. const segments = _boundSegments(this, {property, start: value, end: value});
  9194. if (!segments.length) {
  9195. return;
  9196. }
  9197. const result = [];
  9198. const _interpolate = _getInterpolationMethod(options);
  9199. let i, ilen;
  9200. for (i = 0, ilen = segments.length; i < ilen; ++i) {
  9201. const {start, end} = segments[i];
  9202. const p1 = points[start];
  9203. const p2 = points[end];
  9204. if (p1 === p2) {
  9205. result.push(p1);
  9206. continue;
  9207. }
  9208. const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
  9209. const interpolated = _interpolate(p1, p2, t, options.stepped);
  9210. interpolated[property] = point[property];
  9211. result.push(interpolated);
  9212. }
  9213. return result.length === 1 ? result[0] : result;
  9214. }
  9215. pathSegment(ctx, segment, params) {
  9216. const segmentMethod = _getSegmentMethod(this);
  9217. return segmentMethod(ctx, this, segment, params);
  9218. }
  9219. path(ctx, start, count) {
  9220. const segments = this.segments;
  9221. const segmentMethod = _getSegmentMethod(this);
  9222. let loop = this._loop;
  9223. start = start || 0;
  9224. count = count || (this.points.length - start);
  9225. for (const segment of segments) {
  9226. loop &= segmentMethod(ctx, this, segment, {start, end: start + count - 1});
  9227. }
  9228. return !!loop;
  9229. }
  9230. draw(ctx, chartArea, start, count) {
  9231. const options = this.options || {};
  9232. const points = this.points || [];
  9233. if (points.length && options.borderWidth) {
  9234. ctx.save();
  9235. draw(ctx, this, start, count);
  9236. ctx.restore();
  9237. }
  9238. if (this.animated) {
  9239. this._pointsUpdated = false;
  9240. this._path = undefined;
  9241. }
  9242. }
  9243. }
  9244. LineElement.id = 'line';
  9245. LineElement.defaults = {
  9246. borderCapStyle: 'butt',
  9247. borderDash: [],
  9248. borderDashOffset: 0,
  9249. borderJoinStyle: 'miter',
  9250. borderWidth: 3,
  9251. capBezierPoints: true,
  9252. cubicInterpolationMode: 'default',
  9253. fill: false,
  9254. spanGaps: false,
  9255. stepped: false,
  9256. tension: 0,
  9257. };
  9258. LineElement.defaultRoutes = {
  9259. backgroundColor: 'backgroundColor',
  9260. borderColor: 'borderColor'
  9261. };
  9262. LineElement.descriptors = {
  9263. _scriptable: true,
  9264. _indexable: (name) => name !== 'borderDash' && name !== 'fill',
  9265. };
  9266. function inRange$1(el, pos, axis, useFinalPosition) {
  9267. const options = el.options;
  9268. const {[axis]: value} = el.getProps([axis], useFinalPosition);
  9269. return (Math.abs(pos - value) < options.radius + options.hitRadius);
  9270. }
  9271. class PointElement extends Element {
  9272. constructor(cfg) {
  9273. super();
  9274. this.options = undefined;
  9275. this.parsed = undefined;
  9276. this.skip = undefined;
  9277. this.stop = undefined;
  9278. if (cfg) {
  9279. Object.assign(this, cfg);
  9280. }
  9281. }
  9282. inRange(mouseX, mouseY, useFinalPosition) {
  9283. const options = this.options;
  9284. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  9285. return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
  9286. }
  9287. inXRange(mouseX, useFinalPosition) {
  9288. return inRange$1(this, mouseX, 'x', useFinalPosition);
  9289. }
  9290. inYRange(mouseY, useFinalPosition) {
  9291. return inRange$1(this, mouseY, 'y', useFinalPosition);
  9292. }
  9293. getCenterPoint(useFinalPosition) {
  9294. const {x, y} = this.getProps(['x', 'y'], useFinalPosition);
  9295. return {x, y};
  9296. }
  9297. size(options) {
  9298. options = options || this.options || {};
  9299. let radius = options.radius || 0;
  9300. radius = Math.max(radius, radius && options.hoverRadius || 0);
  9301. const borderWidth = radius && options.borderWidth || 0;
  9302. return (radius + borderWidth) * 2;
  9303. }
  9304. draw(ctx, area) {
  9305. const options = this.options;
  9306. if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
  9307. return;
  9308. }
  9309. ctx.strokeStyle = options.borderColor;
  9310. ctx.lineWidth = options.borderWidth;
  9311. ctx.fillStyle = options.backgroundColor;
  9312. drawPoint(ctx, options, this.x, this.y);
  9313. }
  9314. getRange() {
  9315. const options = this.options || {};
  9316. return options.radius + options.hitRadius;
  9317. }
  9318. }
  9319. PointElement.id = 'point';
  9320. PointElement.defaults = {
  9321. borderWidth: 1,
  9322. hitRadius: 1,
  9323. hoverBorderWidth: 1,
  9324. hoverRadius: 4,
  9325. pointStyle: 'circle',
  9326. radius: 3,
  9327. rotation: 0
  9328. };
  9329. PointElement.defaultRoutes = {
  9330. backgroundColor: 'backgroundColor',
  9331. borderColor: 'borderColor'
  9332. };
  9333. function getBarBounds(bar, useFinalPosition) {
  9334. const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
  9335. let left, right, top, bottom, half;
  9336. if (bar.horizontal) {
  9337. half = height / 2;
  9338. left = Math.min(x, base);
  9339. right = Math.max(x, base);
  9340. top = y - half;
  9341. bottom = y + half;
  9342. } else {
  9343. half = width / 2;
  9344. left = x - half;
  9345. right = x + half;
  9346. top = Math.min(y, base);
  9347. bottom = Math.max(y, base);
  9348. }
  9349. return {left, top, right, bottom};
  9350. }
  9351. function skipOrLimit(skip, value, min, max) {
  9352. return skip ? 0 : _limitValue(value, min, max);
  9353. }
  9354. function parseBorderWidth(bar, maxW, maxH) {
  9355. const value = bar.options.borderWidth;
  9356. const skip = bar.borderSkipped;
  9357. const o = toTRBL(value);
  9358. return {
  9359. t: skipOrLimit(skip.top, o.top, 0, maxH),
  9360. r: skipOrLimit(skip.right, o.right, 0, maxW),
  9361. b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
  9362. l: skipOrLimit(skip.left, o.left, 0, maxW)
  9363. };
  9364. }
  9365. function parseBorderRadius(bar, maxW, maxH) {
  9366. const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);
  9367. const value = bar.options.borderRadius;
  9368. const o = toTRBLCorners(value);
  9369. const maxR = Math.min(maxW, maxH);
  9370. const skip = bar.borderSkipped;
  9371. const enableBorder = enableBorderRadius || isObject(value);
  9372. return {
  9373. topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
  9374. topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
  9375. bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
  9376. bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
  9377. };
  9378. }
  9379. function boundingRects(bar) {
  9380. const bounds = getBarBounds(bar);
  9381. const width = bounds.right - bounds.left;
  9382. const height = bounds.bottom - bounds.top;
  9383. const border = parseBorderWidth(bar, width / 2, height / 2);
  9384. const radius = parseBorderRadius(bar, width / 2, height / 2);
  9385. return {
  9386. outer: {
  9387. x: bounds.left,
  9388. y: bounds.top,
  9389. w: width,
  9390. h: height,
  9391. radius
  9392. },
  9393. inner: {
  9394. x: bounds.left + border.l,
  9395. y: bounds.top + border.t,
  9396. w: width - border.l - border.r,
  9397. h: height - border.t - border.b,
  9398. radius: {
  9399. topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
  9400. topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
  9401. bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
  9402. bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
  9403. }
  9404. }
  9405. };
  9406. }
  9407. function inRange(bar, x, y, useFinalPosition) {
  9408. const skipX = x === null;
  9409. const skipY = y === null;
  9410. const skipBoth = skipX && skipY;
  9411. const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
  9412. return bounds
  9413. && (skipX || _isBetween(x, bounds.left, bounds.right))
  9414. && (skipY || _isBetween(y, bounds.top, bounds.bottom));
  9415. }
  9416. function hasRadius(radius) {
  9417. return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  9418. }
  9419. function addNormalRectPath(ctx, rect) {
  9420. ctx.rect(rect.x, rect.y, rect.w, rect.h);
  9421. }
  9422. function inflateRect(rect, amount, refRect = {}) {
  9423. const x = rect.x !== refRect.x ? -amount : 0;
  9424. const y = rect.y !== refRect.y ? -amount : 0;
  9425. const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
  9426. const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
  9427. return {
  9428. x: rect.x + x,
  9429. y: rect.y + y,
  9430. w: rect.w + w,
  9431. h: rect.h + h,
  9432. radius: rect.radius
  9433. };
  9434. }
  9435. class BarElement extends Element {
  9436. constructor(cfg) {
  9437. super();
  9438. this.options = undefined;
  9439. this.horizontal = undefined;
  9440. this.base = undefined;
  9441. this.width = undefined;
  9442. this.height = undefined;
  9443. this.inflateAmount = undefined;
  9444. if (cfg) {
  9445. Object.assign(this, cfg);
  9446. }
  9447. }
  9448. draw(ctx) {
  9449. const {inflateAmount, options: {borderColor, backgroundColor}} = this;
  9450. const {inner, outer} = boundingRects(this);
  9451. const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
  9452. ctx.save();
  9453. if (outer.w !== inner.w || outer.h !== inner.h) {
  9454. ctx.beginPath();
  9455. addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
  9456. ctx.clip();
  9457. addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
  9458. ctx.fillStyle = borderColor;
  9459. ctx.fill('evenodd');
  9460. }
  9461. ctx.beginPath();
  9462. addRectPath(ctx, inflateRect(inner, inflateAmount));
  9463. ctx.fillStyle = backgroundColor;
  9464. ctx.fill();
  9465. ctx.restore();
  9466. }
  9467. inRange(mouseX, mouseY, useFinalPosition) {
  9468. return inRange(this, mouseX, mouseY, useFinalPosition);
  9469. }
  9470. inXRange(mouseX, useFinalPosition) {
  9471. return inRange(this, mouseX, null, useFinalPosition);
  9472. }
  9473. inYRange(mouseY, useFinalPosition) {
  9474. return inRange(this, null, mouseY, useFinalPosition);
  9475. }
  9476. getCenterPoint(useFinalPosition) {
  9477. const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition);
  9478. return {
  9479. x: horizontal ? (x + base) / 2 : x,
  9480. y: horizontal ? y : (y + base) / 2
  9481. };
  9482. }
  9483. getRange(axis) {
  9484. return axis === 'x' ? this.width / 2 : this.height / 2;
  9485. }
  9486. }
  9487. BarElement.id = 'bar';
  9488. BarElement.defaults = {
  9489. borderSkipped: 'start',
  9490. borderWidth: 0,
  9491. borderRadius: 0,
  9492. inflateAmount: 'auto',
  9493. pointStyle: undefined
  9494. };
  9495. BarElement.defaultRoutes = {
  9496. backgroundColor: 'backgroundColor',
  9497. borderColor: 'borderColor'
  9498. };
  9499. var elements = /*#__PURE__*/Object.freeze({
  9500. __proto__: null,
  9501. ArcElement: ArcElement,
  9502. LineElement: LineElement,
  9503. PointElement: PointElement,
  9504. BarElement: BarElement
  9505. });
  9506. function lttbDecimation(data, start, count, availableWidth, options) {
  9507. const samples = options.samples || availableWidth;
  9508. if (samples >= count) {
  9509. return data.slice(start, start + count);
  9510. }
  9511. const decimated = [];
  9512. const bucketWidth = (count - 2) / (samples - 2);
  9513. let sampledIndex = 0;
  9514. const endIndex = start + count - 1;
  9515. let a = start;
  9516. let i, maxAreaPoint, maxArea, area, nextA;
  9517. decimated[sampledIndex++] = data[a];
  9518. for (i = 0; i < samples - 2; i++) {
  9519. let avgX = 0;
  9520. let avgY = 0;
  9521. let j;
  9522. const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
  9523. const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
  9524. const avgRangeLength = avgRangeEnd - avgRangeStart;
  9525. for (j = avgRangeStart; j < avgRangeEnd; j++) {
  9526. avgX += data[j].x;
  9527. avgY += data[j].y;
  9528. }
  9529. avgX /= avgRangeLength;
  9530. avgY /= avgRangeLength;
  9531. const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
  9532. const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
  9533. const {x: pointAx, y: pointAy} = data[a];
  9534. maxArea = area = -1;
  9535. for (j = rangeOffs; j < rangeTo; j++) {
  9536. area = 0.5 * Math.abs(
  9537. (pointAx - avgX) * (data[j].y - pointAy) -
  9538. (pointAx - data[j].x) * (avgY - pointAy)
  9539. );
  9540. if (area > maxArea) {
  9541. maxArea = area;
  9542. maxAreaPoint = data[j];
  9543. nextA = j;
  9544. }
  9545. }
  9546. decimated[sampledIndex++] = maxAreaPoint;
  9547. a = nextA;
  9548. }
  9549. decimated[sampledIndex++] = data[endIndex];
  9550. return decimated;
  9551. }
  9552. function minMaxDecimation(data, start, count, availableWidth) {
  9553. let avgX = 0;
  9554. let countX = 0;
  9555. let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
  9556. const decimated = [];
  9557. const endIndex = start + count - 1;
  9558. const xMin = data[start].x;
  9559. const xMax = data[endIndex].x;
  9560. const dx = xMax - xMin;
  9561. for (i = start; i < start + count; ++i) {
  9562. point = data[i];
  9563. x = (point.x - xMin) / dx * availableWidth;
  9564. y = point.y;
  9565. const truncX = x | 0;
  9566. if (truncX === prevX) {
  9567. if (y < minY) {
  9568. minY = y;
  9569. minIndex = i;
  9570. } else if (y > maxY) {
  9571. maxY = y;
  9572. maxIndex = i;
  9573. }
  9574. avgX = (countX * avgX + point.x) / ++countX;
  9575. } else {
  9576. const lastIndex = i - 1;
  9577. if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
  9578. const intermediateIndex1 = Math.min(minIndex, maxIndex);
  9579. const intermediateIndex2 = Math.max(minIndex, maxIndex);
  9580. if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
  9581. decimated.push({
  9582. ...data[intermediateIndex1],
  9583. x: avgX,
  9584. });
  9585. }
  9586. if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
  9587. decimated.push({
  9588. ...data[intermediateIndex2],
  9589. x: avgX
  9590. });
  9591. }
  9592. }
  9593. if (i > 0 && lastIndex !== startIndex) {
  9594. decimated.push(data[lastIndex]);
  9595. }
  9596. decimated.push(point);
  9597. prevX = truncX;
  9598. countX = 0;
  9599. minY = maxY = y;
  9600. minIndex = maxIndex = startIndex = i;
  9601. }
  9602. }
  9603. return decimated;
  9604. }
  9605. function cleanDecimatedDataset(dataset) {
  9606. if (dataset._decimated) {
  9607. const data = dataset._data;
  9608. delete dataset._decimated;
  9609. delete dataset._data;
  9610. Object.defineProperty(dataset, 'data', {value: data});
  9611. }
  9612. }
  9613. function cleanDecimatedData(chart) {
  9614. chart.data.datasets.forEach((dataset) => {
  9615. cleanDecimatedDataset(dataset);
  9616. });
  9617. }
  9618. function getStartAndCountOfVisiblePointsSimplified(meta, points) {
  9619. const pointCount = points.length;
  9620. let start = 0;
  9621. let count;
  9622. const {iScale} = meta;
  9623. const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
  9624. if (minDefined) {
  9625. start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
  9626. }
  9627. if (maxDefined) {
  9628. count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
  9629. } else {
  9630. count = pointCount - start;
  9631. }
  9632. return {start, count};
  9633. }
  9634. var plugin_decimation = {
  9635. id: 'decimation',
  9636. defaults: {
  9637. algorithm: 'min-max',
  9638. enabled: false,
  9639. },
  9640. beforeElementsUpdate: (chart, args, options) => {
  9641. if (!options.enabled) {
  9642. cleanDecimatedData(chart);
  9643. return;
  9644. }
  9645. const availableWidth = chart.width;
  9646. chart.data.datasets.forEach((dataset, datasetIndex) => {
  9647. const {_data, indexAxis} = dataset;
  9648. const meta = chart.getDatasetMeta(datasetIndex);
  9649. const data = _data || dataset.data;
  9650. if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {
  9651. return;
  9652. }
  9653. if (!meta.controller.supportsDecimation) {
  9654. return;
  9655. }
  9656. const xAxis = chart.scales[meta.xAxisID];
  9657. if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
  9658. return;
  9659. }
  9660. if (chart.options.parsing) {
  9661. return;
  9662. }
  9663. let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);
  9664. const threshold = options.threshold || 4 * availableWidth;
  9665. if (count <= threshold) {
  9666. cleanDecimatedDataset(dataset);
  9667. return;
  9668. }
  9669. if (isNullOrUndef(_data)) {
  9670. dataset._data = data;
  9671. delete dataset.data;
  9672. Object.defineProperty(dataset, 'data', {
  9673. configurable: true,
  9674. enumerable: true,
  9675. get: function() {
  9676. return this._decimated;
  9677. },
  9678. set: function(d) {
  9679. this._data = d;
  9680. }
  9681. });
  9682. }
  9683. let decimated;
  9684. switch (options.algorithm) {
  9685. case 'lttb':
  9686. decimated = lttbDecimation(data, start, count, availableWidth, options);
  9687. break;
  9688. case 'min-max':
  9689. decimated = minMaxDecimation(data, start, count, availableWidth);
  9690. break;
  9691. default:
  9692. throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
  9693. }
  9694. dataset._decimated = decimated;
  9695. });
  9696. },
  9697. destroy(chart) {
  9698. cleanDecimatedData(chart);
  9699. }
  9700. };
  9701. function _segments(line, target, property) {
  9702. const segments = line.segments;
  9703. const points = line.points;
  9704. const tpoints = target.points;
  9705. const parts = [];
  9706. for (const segment of segments) {
  9707. let {start, end} = segment;
  9708. end = _findSegmentEnd(start, end, points);
  9709. const bounds = _getBounds(property, points[start], points[end], segment.loop);
  9710. if (!target.segments) {
  9711. parts.push({
  9712. source: segment,
  9713. target: bounds,
  9714. start: points[start],
  9715. end: points[end]
  9716. });
  9717. continue;
  9718. }
  9719. const targetSegments = _boundSegments(target, bounds);
  9720. for (const tgt of targetSegments) {
  9721. const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
  9722. const fillSources = _boundSegment(segment, points, subBounds);
  9723. for (const fillSource of fillSources) {
  9724. parts.push({
  9725. source: fillSource,
  9726. target: tgt,
  9727. start: {
  9728. [property]: _getEdge(bounds, subBounds, 'start', Math.max)
  9729. },
  9730. end: {
  9731. [property]: _getEdge(bounds, subBounds, 'end', Math.min)
  9732. }
  9733. });
  9734. }
  9735. }
  9736. }
  9737. return parts;
  9738. }
  9739. function _getBounds(property, first, last, loop) {
  9740. if (loop) {
  9741. return;
  9742. }
  9743. let start = first[property];
  9744. let end = last[property];
  9745. if (property === 'angle') {
  9746. start = _normalizeAngle(start);
  9747. end = _normalizeAngle(end);
  9748. }
  9749. return {property, start, end};
  9750. }
  9751. function _pointsFromSegments(boundary, line) {
  9752. const {x = null, y = null} = boundary || {};
  9753. const linePoints = line.points;
  9754. const points = [];
  9755. line.segments.forEach(({start, end}) => {
  9756. end = _findSegmentEnd(start, end, linePoints);
  9757. const first = linePoints[start];
  9758. const last = linePoints[end];
  9759. if (y !== null) {
  9760. points.push({x: first.x, y});
  9761. points.push({x: last.x, y});
  9762. } else if (x !== null) {
  9763. points.push({x, y: first.y});
  9764. points.push({x, y: last.y});
  9765. }
  9766. });
  9767. return points;
  9768. }
  9769. function _findSegmentEnd(start, end, points) {
  9770. for (;end > start; end--) {
  9771. const point = points[end];
  9772. if (!isNaN(point.x) && !isNaN(point.y)) {
  9773. break;
  9774. }
  9775. }
  9776. return end;
  9777. }
  9778. function _getEdge(a, b, prop, fn) {
  9779. if (a && b) {
  9780. return fn(a[prop], b[prop]);
  9781. }
  9782. return a ? a[prop] : b ? b[prop] : 0;
  9783. }
  9784. function _createBoundaryLine(boundary, line) {
  9785. let points = [];
  9786. let _loop = false;
  9787. if (isArray(boundary)) {
  9788. _loop = true;
  9789. points = boundary;
  9790. } else {
  9791. points = _pointsFromSegments(boundary, line);
  9792. }
  9793. return points.length ? new LineElement({
  9794. points,
  9795. options: {tension: 0},
  9796. _loop,
  9797. _fullLoop: _loop
  9798. }) : null;
  9799. }
  9800. function _resolveTarget(sources, index, propagate) {
  9801. const source = sources[index];
  9802. let fill = source.fill;
  9803. const visited = [index];
  9804. let target;
  9805. if (!propagate) {
  9806. return fill;
  9807. }
  9808. while (fill !== false && visited.indexOf(fill) === -1) {
  9809. if (!isNumberFinite(fill)) {
  9810. return fill;
  9811. }
  9812. target = sources[fill];
  9813. if (!target) {
  9814. return false;
  9815. }
  9816. if (target.visible) {
  9817. return fill;
  9818. }
  9819. visited.push(fill);
  9820. fill = target.fill;
  9821. }
  9822. return false;
  9823. }
  9824. function _decodeFill(line, index, count) {
  9825. const fill = parseFillOption(line);
  9826. if (isObject(fill)) {
  9827. return isNaN(fill.value) ? false : fill;
  9828. }
  9829. let target = parseFloat(fill);
  9830. if (isNumberFinite(target) && Math.floor(target) === target) {
  9831. return decodeTargetIndex(fill[0], index, target, count);
  9832. }
  9833. return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;
  9834. }
  9835. function decodeTargetIndex(firstCh, index, target, count) {
  9836. if (firstCh === '-' || firstCh === '+') {
  9837. target = index + target;
  9838. }
  9839. if (target === index || target < 0 || target >= count) {
  9840. return false;
  9841. }
  9842. return target;
  9843. }
  9844. function _getTargetPixel(fill, scale) {
  9845. let pixel = null;
  9846. if (fill === 'start') {
  9847. pixel = scale.bottom;
  9848. } else if (fill === 'end') {
  9849. pixel = scale.top;
  9850. } else if (isObject(fill)) {
  9851. pixel = scale.getPixelForValue(fill.value);
  9852. } else if (scale.getBasePixel) {
  9853. pixel = scale.getBasePixel();
  9854. }
  9855. return pixel;
  9856. }
  9857. function _getTargetValue(fill, scale, startValue) {
  9858. let value;
  9859. if (fill === 'start') {
  9860. value = startValue;
  9861. } else if (fill === 'end') {
  9862. value = scale.options.reverse ? scale.min : scale.max;
  9863. } else if (isObject(fill)) {
  9864. value = fill.value;
  9865. } else {
  9866. value = scale.getBaseValue();
  9867. }
  9868. return value;
  9869. }
  9870. function parseFillOption(line) {
  9871. const options = line.options;
  9872. const fillOption = options.fill;
  9873. let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
  9874. if (fill === undefined) {
  9875. fill = !!options.backgroundColor;
  9876. }
  9877. if (fill === false || fill === null) {
  9878. return false;
  9879. }
  9880. if (fill === true) {
  9881. return 'origin';
  9882. }
  9883. return fill;
  9884. }
  9885. function _buildStackLine(source) {
  9886. const {scale, index, line} = source;
  9887. const points = [];
  9888. const segments = line.segments;
  9889. const sourcePoints = line.points;
  9890. const linesBelow = getLinesBelow(scale, index);
  9891. linesBelow.push(_createBoundaryLine({x: null, y: scale.bottom}, line));
  9892. for (let i = 0; i < segments.length; i++) {
  9893. const segment = segments[i];
  9894. for (let j = segment.start; j <= segment.end; j++) {
  9895. addPointsBelow(points, sourcePoints[j], linesBelow);
  9896. }
  9897. }
  9898. return new LineElement({points, options: {}});
  9899. }
  9900. function getLinesBelow(scale, index) {
  9901. const below = [];
  9902. const metas = scale.getMatchingVisibleMetas('line');
  9903. for (let i = 0; i < metas.length; i++) {
  9904. const meta = metas[i];
  9905. if (meta.index === index) {
  9906. break;
  9907. }
  9908. if (!meta.hidden) {
  9909. below.unshift(meta.dataset);
  9910. }
  9911. }
  9912. return below;
  9913. }
  9914. function addPointsBelow(points, sourcePoint, linesBelow) {
  9915. const postponed = [];
  9916. for (let j = 0; j < linesBelow.length; j++) {
  9917. const line = linesBelow[j];
  9918. const {first, last, point} = findPoint(line, sourcePoint, 'x');
  9919. if (!point || (first && last)) {
  9920. continue;
  9921. }
  9922. if (first) {
  9923. postponed.unshift(point);
  9924. } else {
  9925. points.push(point);
  9926. if (!last) {
  9927. break;
  9928. }
  9929. }
  9930. }
  9931. points.push(...postponed);
  9932. }
  9933. function findPoint(line, sourcePoint, property) {
  9934. const point = line.interpolate(sourcePoint, property);
  9935. if (!point) {
  9936. return {};
  9937. }
  9938. const pointValue = point[property];
  9939. const segments = line.segments;
  9940. const linePoints = line.points;
  9941. let first = false;
  9942. let last = false;
  9943. for (let i = 0; i < segments.length; i++) {
  9944. const segment = segments[i];
  9945. const firstValue = linePoints[segment.start][property];
  9946. const lastValue = linePoints[segment.end][property];
  9947. if (_isBetween(pointValue, firstValue, lastValue)) {
  9948. first = pointValue === firstValue;
  9949. last = pointValue === lastValue;
  9950. break;
  9951. }
  9952. }
  9953. return {first, last, point};
  9954. }
  9955. class simpleArc {
  9956. constructor(opts) {
  9957. this.x = opts.x;
  9958. this.y = opts.y;
  9959. this.radius = opts.radius;
  9960. }
  9961. pathSegment(ctx, bounds, opts) {
  9962. const {x, y, radius} = this;
  9963. bounds = bounds || {start: 0, end: TAU};
  9964. ctx.arc(x, y, radius, bounds.end, bounds.start, true);
  9965. return !opts.bounds;
  9966. }
  9967. interpolate(point) {
  9968. const {x, y, radius} = this;
  9969. const angle = point.angle;
  9970. return {
  9971. x: x + Math.cos(angle) * radius,
  9972. y: y + Math.sin(angle) * radius,
  9973. angle
  9974. };
  9975. }
  9976. }
  9977. function _getTarget(source) {
  9978. const {chart, fill, line} = source;
  9979. if (isNumberFinite(fill)) {
  9980. return getLineByIndex(chart, fill);
  9981. }
  9982. if (fill === 'stack') {
  9983. return _buildStackLine(source);
  9984. }
  9985. if (fill === 'shape') {
  9986. return true;
  9987. }
  9988. const boundary = computeBoundary(source);
  9989. if (boundary instanceof simpleArc) {
  9990. return boundary;
  9991. }
  9992. return _createBoundaryLine(boundary, line);
  9993. }
  9994. function getLineByIndex(chart, index) {
  9995. const meta = chart.getDatasetMeta(index);
  9996. const visible = meta && chart.isDatasetVisible(index);
  9997. return visible ? meta.dataset : null;
  9998. }
  9999. function computeBoundary(source) {
  10000. const scale = source.scale || {};
  10001. if (scale.getPointPositionForValue) {
  10002. return computeCircularBoundary(source);
  10003. }
  10004. return computeLinearBoundary(source);
  10005. }
  10006. function computeLinearBoundary(source) {
  10007. const {scale = {}, fill} = source;
  10008. const pixel = _getTargetPixel(fill, scale);
  10009. if (isNumberFinite(pixel)) {
  10010. const horizontal = scale.isHorizontal();
  10011. return {
  10012. x: horizontal ? pixel : null,
  10013. y: horizontal ? null : pixel
  10014. };
  10015. }
  10016. return null;
  10017. }
  10018. function computeCircularBoundary(source) {
  10019. const {scale, fill} = source;
  10020. const options = scale.options;
  10021. const length = scale.getLabels().length;
  10022. const start = options.reverse ? scale.max : scale.min;
  10023. const value = _getTargetValue(fill, scale, start);
  10024. const target = [];
  10025. if (options.grid.circular) {
  10026. const center = scale.getPointPositionForValue(0, start);
  10027. return new simpleArc({
  10028. x: center.x,
  10029. y: center.y,
  10030. radius: scale.getDistanceFromCenterForValue(value)
  10031. });
  10032. }
  10033. for (let i = 0; i < length; ++i) {
  10034. target.push(scale.getPointPositionForValue(i, value));
  10035. }
  10036. return target;
  10037. }
  10038. function _drawfill(ctx, source, area) {
  10039. const target = _getTarget(source);
  10040. const {line, scale, axis} = source;
  10041. const lineOpts = line.options;
  10042. const fillOption = lineOpts.fill;
  10043. const color = lineOpts.backgroundColor;
  10044. const {above = color, below = color} = fillOption || {};
  10045. if (target && line.points.length) {
  10046. clipArea(ctx, area);
  10047. doFill(ctx, {line, target, above, below, area, scale, axis});
  10048. unclipArea(ctx);
  10049. }
  10050. }
  10051. function doFill(ctx, cfg) {
  10052. const {line, target, above, below, area, scale} = cfg;
  10053. const property = line._loop ? 'angle' : cfg.axis;
  10054. ctx.save();
  10055. if (property === 'x' && below !== above) {
  10056. clipVertical(ctx, target, area.top);
  10057. fill(ctx, {line, target, color: above, scale, property});
  10058. ctx.restore();
  10059. ctx.save();
  10060. clipVertical(ctx, target, area.bottom);
  10061. }
  10062. fill(ctx, {line, target, color: below, scale, property});
  10063. ctx.restore();
  10064. }
  10065. function clipVertical(ctx, target, clipY) {
  10066. const {segments, points} = target;
  10067. let first = true;
  10068. let lineLoop = false;
  10069. ctx.beginPath();
  10070. for (const segment of segments) {
  10071. const {start, end} = segment;
  10072. const firstPoint = points[start];
  10073. const lastPoint = points[_findSegmentEnd(start, end, points)];
  10074. if (first) {
  10075. ctx.moveTo(firstPoint.x, firstPoint.y);
  10076. first = false;
  10077. } else {
  10078. ctx.lineTo(firstPoint.x, clipY);
  10079. ctx.lineTo(firstPoint.x, firstPoint.y);
  10080. }
  10081. lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});
  10082. if (lineLoop) {
  10083. ctx.closePath();
  10084. } else {
  10085. ctx.lineTo(lastPoint.x, clipY);
  10086. }
  10087. }
  10088. ctx.lineTo(target.first().x, clipY);
  10089. ctx.closePath();
  10090. ctx.clip();
  10091. }
  10092. function fill(ctx, cfg) {
  10093. const {line, target, property, color, scale} = cfg;
  10094. const segments = _segments(line, target, property);
  10095. for (const {source: src, target: tgt, start, end} of segments) {
  10096. const {style: {backgroundColor = color} = {}} = src;
  10097. const notShape = target !== true;
  10098. ctx.save();
  10099. ctx.fillStyle = backgroundColor;
  10100. clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
  10101. ctx.beginPath();
  10102. const lineLoop = !!line.pathSegment(ctx, src);
  10103. let loop;
  10104. if (notShape) {
  10105. if (lineLoop) {
  10106. ctx.closePath();
  10107. } else {
  10108. interpolatedLineTo(ctx, target, end, property);
  10109. }
  10110. const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});
  10111. loop = lineLoop && targetLoop;
  10112. if (!loop) {
  10113. interpolatedLineTo(ctx, target, start, property);
  10114. }
  10115. }
  10116. ctx.closePath();
  10117. ctx.fill(loop ? 'evenodd' : 'nonzero');
  10118. ctx.restore();
  10119. }
  10120. }
  10121. function clipBounds(ctx, scale, bounds) {
  10122. const {top, bottom} = scale.chart.chartArea;
  10123. const {property, start, end} = bounds || {};
  10124. if (property === 'x') {
  10125. ctx.beginPath();
  10126. ctx.rect(start, top, end - start, bottom - top);
  10127. ctx.clip();
  10128. }
  10129. }
  10130. function interpolatedLineTo(ctx, target, point, property) {
  10131. const interpolatedPoint = target.interpolate(point, property);
  10132. if (interpolatedPoint) {
  10133. ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
  10134. }
  10135. }
  10136. var index = {
  10137. id: 'filler',
  10138. afterDatasetsUpdate(chart, _args, options) {
  10139. const count = (chart.data.datasets || []).length;
  10140. const sources = [];
  10141. let meta, i, line, source;
  10142. for (i = 0; i < count; ++i) {
  10143. meta = chart.getDatasetMeta(i);
  10144. line = meta.dataset;
  10145. source = null;
  10146. if (line && line.options && line instanceof LineElement) {
  10147. source = {
  10148. visible: chart.isDatasetVisible(i),
  10149. index: i,
  10150. fill: _decodeFill(line, i, count),
  10151. chart,
  10152. axis: meta.controller.options.indexAxis,
  10153. scale: meta.vScale,
  10154. line,
  10155. };
  10156. }
  10157. meta.$filler = source;
  10158. sources.push(source);
  10159. }
  10160. for (i = 0; i < count; ++i) {
  10161. source = sources[i];
  10162. if (!source || source.fill === false) {
  10163. continue;
  10164. }
  10165. source.fill = _resolveTarget(sources, i, options.propagate);
  10166. }
  10167. },
  10168. beforeDraw(chart, _args, options) {
  10169. const draw = options.drawTime === 'beforeDraw';
  10170. const metasets = chart.getSortedVisibleDatasetMetas();
  10171. const area = chart.chartArea;
  10172. for (let i = metasets.length - 1; i >= 0; --i) {
  10173. const source = metasets[i].$filler;
  10174. if (!source) {
  10175. continue;
  10176. }
  10177. source.line.updateControlPoints(area, source.axis);
  10178. if (draw) {
  10179. _drawfill(chart.ctx, source, area);
  10180. }
  10181. }
  10182. },
  10183. beforeDatasetsDraw(chart, _args, options) {
  10184. if (options.drawTime !== 'beforeDatasetsDraw') {
  10185. return;
  10186. }
  10187. const metasets = chart.getSortedVisibleDatasetMetas();
  10188. for (let i = metasets.length - 1; i >= 0; --i) {
  10189. const source = metasets[i].$filler;
  10190. if (source) {
  10191. _drawfill(chart.ctx, source, chart.chartArea);
  10192. }
  10193. }
  10194. },
  10195. beforeDatasetDraw(chart, args, options) {
  10196. const source = args.meta.$filler;
  10197. if (!source || source.fill === false || options.drawTime !== 'beforeDatasetDraw') {
  10198. return;
  10199. }
  10200. _drawfill(chart.ctx, source, chart.chartArea);
  10201. },
  10202. defaults: {
  10203. propagate: true,
  10204. drawTime: 'beforeDatasetDraw'
  10205. }
  10206. };
  10207. const getBoxSize = (labelOpts, fontSize) => {
  10208. let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts;
  10209. if (labelOpts.usePointStyle) {
  10210. boxHeight = Math.min(boxHeight, fontSize);
  10211. boxWidth = Math.min(boxWidth, fontSize);
  10212. }
  10213. return {
  10214. boxWidth,
  10215. boxHeight,
  10216. itemHeight: Math.max(fontSize, boxHeight)
  10217. };
  10218. };
  10219. const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  10220. class Legend extends Element {
  10221. constructor(config) {
  10222. super();
  10223. this._added = false;
  10224. this.legendHitBoxes = [];
  10225. this._hoveredItem = null;
  10226. this.doughnutMode = false;
  10227. this.chart = config.chart;
  10228. this.options = config.options;
  10229. this.ctx = config.ctx;
  10230. this.legendItems = undefined;
  10231. this.columnSizes = undefined;
  10232. this.lineWidths = undefined;
  10233. this.maxHeight = undefined;
  10234. this.maxWidth = undefined;
  10235. this.top = undefined;
  10236. this.bottom = undefined;
  10237. this.left = undefined;
  10238. this.right = undefined;
  10239. this.height = undefined;
  10240. this.width = undefined;
  10241. this._margins = undefined;
  10242. this.position = undefined;
  10243. this.weight = undefined;
  10244. this.fullSize = undefined;
  10245. }
  10246. update(maxWidth, maxHeight, margins) {
  10247. this.maxWidth = maxWidth;
  10248. this.maxHeight = maxHeight;
  10249. this._margins = margins;
  10250. this.setDimensions();
  10251. this.buildLabels();
  10252. this.fit();
  10253. }
  10254. setDimensions() {
  10255. if (this.isHorizontal()) {
  10256. this.width = this.maxWidth;
  10257. this.left = this._margins.left;
  10258. this.right = this.width;
  10259. } else {
  10260. this.height = this.maxHeight;
  10261. this.top = this._margins.top;
  10262. this.bottom = this.height;
  10263. }
  10264. }
  10265. buildLabels() {
  10266. const labelOpts = this.options.labels || {};
  10267. let legendItems = callback(labelOpts.generateLabels, [this.chart], this) || [];
  10268. if (labelOpts.filter) {
  10269. legendItems = legendItems.filter((item) => labelOpts.filter(item, this.chart.data));
  10270. }
  10271. if (labelOpts.sort) {
  10272. legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, this.chart.data));
  10273. }
  10274. if (this.options.reverse) {
  10275. legendItems.reverse();
  10276. }
  10277. this.legendItems = legendItems;
  10278. }
  10279. fit() {
  10280. const {options, ctx} = this;
  10281. if (!options.display) {
  10282. this.width = this.height = 0;
  10283. return;
  10284. }
  10285. const labelOpts = options.labels;
  10286. const labelFont = toFont(labelOpts.font);
  10287. const fontSize = labelFont.size;
  10288. const titleHeight = this._computeTitleHeight();
  10289. const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize);
  10290. let width, height;
  10291. ctx.font = labelFont.string;
  10292. if (this.isHorizontal()) {
  10293. width = this.maxWidth;
  10294. height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  10295. } else {
  10296. height = this.maxHeight;
  10297. width = this._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10;
  10298. }
  10299. this.width = Math.min(width, options.maxWidth || this.maxWidth);
  10300. this.height = Math.min(height, options.maxHeight || this.maxHeight);
  10301. }
  10302. _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
  10303. const {ctx, maxWidth, options: {labels: {padding}}} = this;
  10304. const hitboxes = this.legendHitBoxes = [];
  10305. const lineWidths = this.lineWidths = [0];
  10306. const lineHeight = itemHeight + padding;
  10307. let totalHeight = titleHeight;
  10308. ctx.textAlign = 'left';
  10309. ctx.textBaseline = 'middle';
  10310. let row = -1;
  10311. let top = -lineHeight;
  10312. this.legendItems.forEach((legendItem, i) => {
  10313. const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  10314. if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
  10315. totalHeight += lineHeight;
  10316. lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
  10317. top += lineHeight;
  10318. row++;
  10319. }
  10320. hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight};
  10321. lineWidths[lineWidths.length - 1] += itemWidth + padding;
  10322. });
  10323. return totalHeight;
  10324. }
  10325. _fitCols(titleHeight, fontSize, boxWidth, itemHeight) {
  10326. const {ctx, maxHeight, options: {labels: {padding}}} = this;
  10327. const hitboxes = this.legendHitBoxes = [];
  10328. const columnSizes = this.columnSizes = [];
  10329. const heightLimit = maxHeight - titleHeight;
  10330. let totalWidth = padding;
  10331. let currentColWidth = 0;
  10332. let currentColHeight = 0;
  10333. let left = 0;
  10334. let col = 0;
  10335. this.legendItems.forEach((legendItem, i) => {
  10336. const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  10337. if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
  10338. totalWidth += currentColWidth + padding;
  10339. columnSizes.push({width: currentColWidth, height: currentColHeight});
  10340. left += currentColWidth + padding;
  10341. col++;
  10342. currentColWidth = currentColHeight = 0;
  10343. }
  10344. hitboxes[i] = {left, top: currentColHeight, col, width: itemWidth, height: itemHeight};
  10345. currentColWidth = Math.max(currentColWidth, itemWidth);
  10346. currentColHeight += itemHeight + padding;
  10347. });
  10348. totalWidth += currentColWidth;
  10349. columnSizes.push({width: currentColWidth, height: currentColHeight});
  10350. return totalWidth;
  10351. }
  10352. adjustHitBoxes() {
  10353. if (!this.options.display) {
  10354. return;
  10355. }
  10356. const titleHeight = this._computeTitleHeight();
  10357. const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this;
  10358. const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
  10359. if (this.isHorizontal()) {
  10360. let row = 0;
  10361. let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  10362. for (const hitbox of hitboxes) {
  10363. if (row !== hitbox.row) {
  10364. row = hitbox.row;
  10365. left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
  10366. }
  10367. hitbox.top += this.top + titleHeight + padding;
  10368. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
  10369. left += hitbox.width + padding;
  10370. }
  10371. } else {
  10372. let col = 0;
  10373. let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  10374. for (const hitbox of hitboxes) {
  10375. if (hitbox.col !== col) {
  10376. col = hitbox.col;
  10377. top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
  10378. }
  10379. hitbox.top = top;
  10380. hitbox.left += this.left + padding;
  10381. hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
  10382. top += hitbox.height + padding;
  10383. }
  10384. }
  10385. }
  10386. isHorizontal() {
  10387. return this.options.position === 'top' || this.options.position === 'bottom';
  10388. }
  10389. draw() {
  10390. if (this.options.display) {
  10391. const ctx = this.ctx;
  10392. clipArea(ctx, this);
  10393. this._draw();
  10394. unclipArea(ctx);
  10395. }
  10396. }
  10397. _draw() {
  10398. const {options: opts, columnSizes, lineWidths, ctx} = this;
  10399. const {align, labels: labelOpts} = opts;
  10400. const defaultColor = defaults.color;
  10401. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  10402. const labelFont = toFont(labelOpts.font);
  10403. const {color: fontColor, padding} = labelOpts;
  10404. const fontSize = labelFont.size;
  10405. const halfFontSize = fontSize / 2;
  10406. let cursor;
  10407. this.drawTitle();
  10408. ctx.textAlign = rtlHelper.textAlign('left');
  10409. ctx.textBaseline = 'middle';
  10410. ctx.lineWidth = 0.5;
  10411. ctx.font = labelFont.string;
  10412. const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize);
  10413. const drawLegendBox = function(x, y, legendItem) {
  10414. if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
  10415. return;
  10416. }
  10417. ctx.save();
  10418. const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
  10419. ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
  10420. ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
  10421. ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
  10422. ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
  10423. ctx.lineWidth = lineWidth;
  10424. ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
  10425. ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
  10426. if (labelOpts.usePointStyle) {
  10427. const drawOptions = {
  10428. radius: boxWidth * Math.SQRT2 / 2,
  10429. pointStyle: legendItem.pointStyle,
  10430. rotation: legendItem.rotation,
  10431. borderWidth: lineWidth
  10432. };
  10433. const centerX = rtlHelper.xPlus(x, boxWidth / 2);
  10434. const centerY = y + halfFontSize;
  10435. drawPoint(ctx, drawOptions, centerX, centerY);
  10436. } else {
  10437. const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
  10438. const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
  10439. const borderRadius = toTRBLCorners(legendItem.borderRadius);
  10440. ctx.beginPath();
  10441. if (Object.values(borderRadius).some(v => v !== 0)) {
  10442. addRoundedRectPath(ctx, {
  10443. x: xBoxLeft,
  10444. y: yBoxTop,
  10445. w: boxWidth,
  10446. h: boxHeight,
  10447. radius: borderRadius,
  10448. });
  10449. } else {
  10450. ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
  10451. }
  10452. ctx.fill();
  10453. if (lineWidth !== 0) {
  10454. ctx.stroke();
  10455. }
  10456. }
  10457. ctx.restore();
  10458. };
  10459. const fillText = function(x, y, legendItem) {
  10460. renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {
  10461. strikethrough: legendItem.hidden,
  10462. textAlign: rtlHelper.textAlign(legendItem.textAlign)
  10463. });
  10464. };
  10465. const isHorizontal = this.isHorizontal();
  10466. const titleHeight = this._computeTitleHeight();
  10467. if (isHorizontal) {
  10468. cursor = {
  10469. x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
  10470. y: this.top + padding + titleHeight,
  10471. line: 0
  10472. };
  10473. } else {
  10474. cursor = {
  10475. x: this.left + padding,
  10476. y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
  10477. line: 0
  10478. };
  10479. }
  10480. overrideTextDirection(this.ctx, opts.textDirection);
  10481. const lineHeight = itemHeight + padding;
  10482. this.legendItems.forEach((legendItem, i) => {
  10483. ctx.strokeStyle = legendItem.fontColor || fontColor;
  10484. ctx.fillStyle = legendItem.fontColor || fontColor;
  10485. const textWidth = ctx.measureText(legendItem.text).width;
  10486. const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
  10487. const width = boxWidth + halfFontSize + textWidth;
  10488. let x = cursor.x;
  10489. let y = cursor.y;
  10490. rtlHelper.setWidth(this.width);
  10491. if (isHorizontal) {
  10492. if (i > 0 && x + width + padding > this.right) {
  10493. y = cursor.y += lineHeight;
  10494. cursor.line++;
  10495. x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
  10496. }
  10497. } else if (i > 0 && y + lineHeight > this.bottom) {
  10498. x = cursor.x = x + columnSizes[cursor.line].width + padding;
  10499. cursor.line++;
  10500. y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
  10501. }
  10502. const realX = rtlHelper.x(x);
  10503. drawLegendBox(realX, y, legendItem);
  10504. x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
  10505. fillText(rtlHelper.x(x), y, legendItem);
  10506. if (isHorizontal) {
  10507. cursor.x += width + padding;
  10508. } else {
  10509. cursor.y += lineHeight;
  10510. }
  10511. });
  10512. restoreTextDirection(this.ctx, opts.textDirection);
  10513. }
  10514. drawTitle() {
  10515. const opts = this.options;
  10516. const titleOpts = opts.title;
  10517. const titleFont = toFont(titleOpts.font);
  10518. const titlePadding = toPadding(titleOpts.padding);
  10519. if (!titleOpts.display) {
  10520. return;
  10521. }
  10522. const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
  10523. const ctx = this.ctx;
  10524. const position = titleOpts.position;
  10525. const halfFontSize = titleFont.size / 2;
  10526. const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
  10527. let y;
  10528. let left = this.left;
  10529. let maxWidth = this.width;
  10530. if (this.isHorizontal()) {
  10531. maxWidth = Math.max(...this.lineWidths);
  10532. y = this.top + topPaddingPlusHalfFontSize;
  10533. left = _alignStartEnd(opts.align, left, this.right - maxWidth);
  10534. } else {
  10535. const maxHeight = this.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0);
  10536. y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
  10537. }
  10538. const x = _alignStartEnd(position, left, left + maxWidth);
  10539. ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
  10540. ctx.textBaseline = 'middle';
  10541. ctx.strokeStyle = titleOpts.color;
  10542. ctx.fillStyle = titleOpts.color;
  10543. ctx.font = titleFont.string;
  10544. renderText(ctx, titleOpts.text, x, y, titleFont);
  10545. }
  10546. _computeTitleHeight() {
  10547. const titleOpts = this.options.title;
  10548. const titleFont = toFont(titleOpts.font);
  10549. const titlePadding = toPadding(titleOpts.padding);
  10550. return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
  10551. }
  10552. _getLegendItemAt(x, y) {
  10553. let i, hitBox, lh;
  10554. if (_isBetween(x, this.left, this.right)
  10555. && _isBetween(y, this.top, this.bottom)) {
  10556. lh = this.legendHitBoxes;
  10557. for (i = 0; i < lh.length; ++i) {
  10558. hitBox = lh[i];
  10559. if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width)
  10560. && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
  10561. return this.legendItems[i];
  10562. }
  10563. }
  10564. }
  10565. return null;
  10566. }
  10567. handleEvent(e) {
  10568. const opts = this.options;
  10569. if (!isListened(e.type, opts)) {
  10570. return;
  10571. }
  10572. const hoveredItem = this._getLegendItemAt(e.x, e.y);
  10573. if (e.type === 'mousemove') {
  10574. const previous = this._hoveredItem;
  10575. const sameItem = itemsEqual(previous, hoveredItem);
  10576. if (previous && !sameItem) {
  10577. callback(opts.onLeave, [e, previous, this], this);
  10578. }
  10579. this._hoveredItem = hoveredItem;
  10580. if (hoveredItem && !sameItem) {
  10581. callback(opts.onHover, [e, hoveredItem, this], this);
  10582. }
  10583. } else if (hoveredItem) {
  10584. callback(opts.onClick, [e, hoveredItem, this], this);
  10585. }
  10586. }
  10587. }
  10588. function isListened(type, opts) {
  10589. if (type === 'mousemove' && (opts.onHover || opts.onLeave)) {
  10590. return true;
  10591. }
  10592. if (opts.onClick && (type === 'click' || type === 'mouseup')) {
  10593. return true;
  10594. }
  10595. return false;
  10596. }
  10597. var plugin_legend = {
  10598. id: 'legend',
  10599. _element: Legend,
  10600. start(chart, _args, options) {
  10601. const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart});
  10602. layouts.configure(chart, legend, options);
  10603. layouts.addBox(chart, legend);
  10604. },
  10605. stop(chart) {
  10606. layouts.removeBox(chart, chart.legend);
  10607. delete chart.legend;
  10608. },
  10609. beforeUpdate(chart, _args, options) {
  10610. const legend = chart.legend;
  10611. layouts.configure(chart, legend, options);
  10612. legend.options = options;
  10613. },
  10614. afterUpdate(chart) {
  10615. const legend = chart.legend;
  10616. legend.buildLabels();
  10617. legend.adjustHitBoxes();
  10618. },
  10619. afterEvent(chart, args) {
  10620. if (!args.replay) {
  10621. chart.legend.handleEvent(args.event);
  10622. }
  10623. },
  10624. defaults: {
  10625. display: true,
  10626. position: 'top',
  10627. align: 'center',
  10628. fullSize: true,
  10629. reverse: false,
  10630. weight: 1000,
  10631. onClick(e, legendItem, legend) {
  10632. const index = legendItem.datasetIndex;
  10633. const ci = legend.chart;
  10634. if (ci.isDatasetVisible(index)) {
  10635. ci.hide(index);
  10636. legendItem.hidden = true;
  10637. } else {
  10638. ci.show(index);
  10639. legendItem.hidden = false;
  10640. }
  10641. },
  10642. onHover: null,
  10643. onLeave: null,
  10644. labels: {
  10645. color: (ctx) => ctx.chart.options.color,
  10646. boxWidth: 40,
  10647. padding: 10,
  10648. generateLabels(chart) {
  10649. const datasets = chart.data.datasets;
  10650. const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options;
  10651. return chart._getSortedDatasetMetas().map((meta) => {
  10652. const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
  10653. const borderWidth = toPadding(style.borderWidth);
  10654. return {
  10655. text: datasets[meta.index].label,
  10656. fillStyle: style.backgroundColor,
  10657. fontColor: color,
  10658. hidden: !meta.visible,
  10659. lineCap: style.borderCapStyle,
  10660. lineDash: style.borderDash,
  10661. lineDashOffset: style.borderDashOffset,
  10662. lineJoin: style.borderJoinStyle,
  10663. lineWidth: (borderWidth.width + borderWidth.height) / 4,
  10664. strokeStyle: style.borderColor,
  10665. pointStyle: pointStyle || style.pointStyle,
  10666. rotation: style.rotation,
  10667. textAlign: textAlign || style.textAlign,
  10668. borderRadius: 0,
  10669. datasetIndex: meta.index
  10670. };
  10671. }, this);
  10672. }
  10673. },
  10674. title: {
  10675. color: (ctx) => ctx.chart.options.color,
  10676. display: false,
  10677. position: 'center',
  10678. text: '',
  10679. }
  10680. },
  10681. descriptors: {
  10682. _scriptable: (name) => !name.startsWith('on'),
  10683. labels: {
  10684. _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name),
  10685. }
  10686. },
  10687. };
  10688. class Title extends Element {
  10689. constructor(config) {
  10690. super();
  10691. this.chart = config.chart;
  10692. this.options = config.options;
  10693. this.ctx = config.ctx;
  10694. this._padding = undefined;
  10695. this.top = undefined;
  10696. this.bottom = undefined;
  10697. this.left = undefined;
  10698. this.right = undefined;
  10699. this.width = undefined;
  10700. this.height = undefined;
  10701. this.position = undefined;
  10702. this.weight = undefined;
  10703. this.fullSize = undefined;
  10704. }
  10705. update(maxWidth, maxHeight) {
  10706. const opts = this.options;
  10707. this.left = 0;
  10708. this.top = 0;
  10709. if (!opts.display) {
  10710. this.width = this.height = this.right = this.bottom = 0;
  10711. return;
  10712. }
  10713. this.width = this.right = maxWidth;
  10714. this.height = this.bottom = maxHeight;
  10715. const lineCount = isArray(opts.text) ? opts.text.length : 1;
  10716. this._padding = toPadding(opts.padding);
  10717. const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
  10718. if (this.isHorizontal()) {
  10719. this.height = textSize;
  10720. } else {
  10721. this.width = textSize;
  10722. }
  10723. }
  10724. isHorizontal() {
  10725. const pos = this.options.position;
  10726. return pos === 'top' || pos === 'bottom';
  10727. }
  10728. _drawArgs(offset) {
  10729. const {top, left, bottom, right, options} = this;
  10730. const align = options.align;
  10731. let rotation = 0;
  10732. let maxWidth, titleX, titleY;
  10733. if (this.isHorizontal()) {
  10734. titleX = _alignStartEnd(align, left, right);
  10735. titleY = top + offset;
  10736. maxWidth = right - left;
  10737. } else {
  10738. if (options.position === 'left') {
  10739. titleX = left + offset;
  10740. titleY = _alignStartEnd(align, bottom, top);
  10741. rotation = PI * -0.5;
  10742. } else {
  10743. titleX = right - offset;
  10744. titleY = _alignStartEnd(align, top, bottom);
  10745. rotation = PI * 0.5;
  10746. }
  10747. maxWidth = bottom - top;
  10748. }
  10749. return {titleX, titleY, maxWidth, rotation};
  10750. }
  10751. draw() {
  10752. const ctx = this.ctx;
  10753. const opts = this.options;
  10754. if (!opts.display) {
  10755. return;
  10756. }
  10757. const fontOpts = toFont(opts.font);
  10758. const lineHeight = fontOpts.lineHeight;
  10759. const offset = lineHeight / 2 + this._padding.top;
  10760. const {titleX, titleY, maxWidth, rotation} = this._drawArgs(offset);
  10761. renderText(ctx, opts.text, 0, 0, fontOpts, {
  10762. color: opts.color,
  10763. maxWidth,
  10764. rotation,
  10765. textAlign: _toLeftRightCenter(opts.align),
  10766. textBaseline: 'middle',
  10767. translation: [titleX, titleY],
  10768. });
  10769. }
  10770. }
  10771. function createTitle(chart, titleOpts) {
  10772. const title = new Title({
  10773. ctx: chart.ctx,
  10774. options: titleOpts,
  10775. chart
  10776. });
  10777. layouts.configure(chart, title, titleOpts);
  10778. layouts.addBox(chart, title);
  10779. chart.titleBlock = title;
  10780. }
  10781. var plugin_title = {
  10782. id: 'title',
  10783. _element: Title,
  10784. start(chart, _args, options) {
  10785. createTitle(chart, options);
  10786. },
  10787. stop(chart) {
  10788. const titleBlock = chart.titleBlock;
  10789. layouts.removeBox(chart, titleBlock);
  10790. delete chart.titleBlock;
  10791. },
  10792. beforeUpdate(chart, _args, options) {
  10793. const title = chart.titleBlock;
  10794. layouts.configure(chart, title, options);
  10795. title.options = options;
  10796. },
  10797. defaults: {
  10798. align: 'center',
  10799. display: false,
  10800. font: {
  10801. weight: 'bold',
  10802. },
  10803. fullSize: true,
  10804. padding: 10,
  10805. position: 'top',
  10806. text: '',
  10807. weight: 2000
  10808. },
  10809. defaultRoutes: {
  10810. color: 'color'
  10811. },
  10812. descriptors: {
  10813. _scriptable: true,
  10814. _indexable: false,
  10815. },
  10816. };
  10817. const map = new WeakMap();
  10818. var plugin_subtitle = {
  10819. id: 'subtitle',
  10820. start(chart, _args, options) {
  10821. const title = new Title({
  10822. ctx: chart.ctx,
  10823. options,
  10824. chart
  10825. });
  10826. layouts.configure(chart, title, options);
  10827. layouts.addBox(chart, title);
  10828. map.set(chart, title);
  10829. },
  10830. stop(chart) {
  10831. layouts.removeBox(chart, map.get(chart));
  10832. map.delete(chart);
  10833. },
  10834. beforeUpdate(chart, _args, options) {
  10835. const title = map.get(chart);
  10836. layouts.configure(chart, title, options);
  10837. title.options = options;
  10838. },
  10839. defaults: {
  10840. align: 'center',
  10841. display: false,
  10842. font: {
  10843. weight: 'normal',
  10844. },
  10845. fullSize: true,
  10846. padding: 0,
  10847. position: 'top',
  10848. text: '',
  10849. weight: 1500
  10850. },
  10851. defaultRoutes: {
  10852. color: 'color'
  10853. },
  10854. descriptors: {
  10855. _scriptable: true,
  10856. _indexable: false,
  10857. },
  10858. };
  10859. const positioners = {
  10860. average(items) {
  10861. if (!items.length) {
  10862. return false;
  10863. }
  10864. let i, len;
  10865. let x = 0;
  10866. let y = 0;
  10867. let count = 0;
  10868. for (i = 0, len = items.length; i < len; ++i) {
  10869. const el = items[i].element;
  10870. if (el && el.hasValue()) {
  10871. const pos = el.tooltipPosition();
  10872. x += pos.x;
  10873. y += pos.y;
  10874. ++count;
  10875. }
  10876. }
  10877. return {
  10878. x: x / count,
  10879. y: y / count
  10880. };
  10881. },
  10882. nearest(items, eventPosition) {
  10883. if (!items.length) {
  10884. return false;
  10885. }
  10886. let x = eventPosition.x;
  10887. let y = eventPosition.y;
  10888. let minDistance = Number.POSITIVE_INFINITY;
  10889. let i, len, nearestElement;
  10890. for (i = 0, len = items.length; i < len; ++i) {
  10891. const el = items[i].element;
  10892. if (el && el.hasValue()) {
  10893. const center = el.getCenterPoint();
  10894. const d = distanceBetweenPoints(eventPosition, center);
  10895. if (d < minDistance) {
  10896. minDistance = d;
  10897. nearestElement = el;
  10898. }
  10899. }
  10900. }
  10901. if (nearestElement) {
  10902. const tp = nearestElement.tooltipPosition();
  10903. x = tp.x;
  10904. y = tp.y;
  10905. }
  10906. return {
  10907. x,
  10908. y
  10909. };
  10910. }
  10911. };
  10912. function pushOrConcat(base, toPush) {
  10913. if (toPush) {
  10914. if (isArray(toPush)) {
  10915. Array.prototype.push.apply(base, toPush);
  10916. } else {
  10917. base.push(toPush);
  10918. }
  10919. }
  10920. return base;
  10921. }
  10922. function splitNewlines(str) {
  10923. if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
  10924. return str.split('\n');
  10925. }
  10926. return str;
  10927. }
  10928. function createTooltipItem(chart, item) {
  10929. const {element, datasetIndex, index} = item;
  10930. const controller = chart.getDatasetMeta(datasetIndex).controller;
  10931. const {label, value} = controller.getLabelAndValue(index);
  10932. return {
  10933. chart,
  10934. label,
  10935. parsed: controller.getParsed(index),
  10936. raw: chart.data.datasets[datasetIndex].data[index],
  10937. formattedValue: value,
  10938. dataset: controller.getDataset(),
  10939. dataIndex: index,
  10940. datasetIndex,
  10941. element
  10942. };
  10943. }
  10944. function getTooltipSize(tooltip, options) {
  10945. const ctx = tooltip.chart.ctx;
  10946. const {body, footer, title} = tooltip;
  10947. const {boxWidth, boxHeight} = options;
  10948. const bodyFont = toFont(options.bodyFont);
  10949. const titleFont = toFont(options.titleFont);
  10950. const footerFont = toFont(options.footerFont);
  10951. const titleLineCount = title.length;
  10952. const footerLineCount = footer.length;
  10953. const bodyLineItemCount = body.length;
  10954. const padding = toPadding(options.padding);
  10955. let height = padding.height;
  10956. let width = 0;
  10957. let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
  10958. combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
  10959. if (titleLineCount) {
  10960. height += titleLineCount * titleFont.lineHeight
  10961. + (titleLineCount - 1) * options.titleSpacing
  10962. + options.titleMarginBottom;
  10963. }
  10964. if (combinedBodyLength) {
  10965. const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
  10966. height += bodyLineItemCount * bodyLineHeight
  10967. + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight
  10968. + (combinedBodyLength - 1) * options.bodySpacing;
  10969. }
  10970. if (footerLineCount) {
  10971. height += options.footerMarginTop
  10972. + footerLineCount * footerFont.lineHeight
  10973. + (footerLineCount - 1) * options.footerSpacing;
  10974. }
  10975. let widthPadding = 0;
  10976. const maxLineWidth = function(line) {
  10977. width = Math.max(width, ctx.measureText(line).width + widthPadding);
  10978. };
  10979. ctx.save();
  10980. ctx.font = titleFont.string;
  10981. each(tooltip.title, maxLineWidth);
  10982. ctx.font = bodyFont.string;
  10983. each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
  10984. widthPadding = options.displayColors ? (boxWidth + 2 + options.boxPadding) : 0;
  10985. each(body, (bodyItem) => {
  10986. each(bodyItem.before, maxLineWidth);
  10987. each(bodyItem.lines, maxLineWidth);
  10988. each(bodyItem.after, maxLineWidth);
  10989. });
  10990. widthPadding = 0;
  10991. ctx.font = footerFont.string;
  10992. each(tooltip.footer, maxLineWidth);
  10993. ctx.restore();
  10994. width += padding.width;
  10995. return {width, height};
  10996. }
  10997. function determineYAlign(chart, size) {
  10998. const {y, height} = size;
  10999. if (y < height / 2) {
  11000. return 'top';
  11001. } else if (y > (chart.height - height / 2)) {
  11002. return 'bottom';
  11003. }
  11004. return 'center';
  11005. }
  11006. function doesNotFitWithAlign(xAlign, chart, options, size) {
  11007. const {x, width} = size;
  11008. const caret = options.caretSize + options.caretPadding;
  11009. if (xAlign === 'left' && x + width + caret > chart.width) {
  11010. return true;
  11011. }
  11012. if (xAlign === 'right' && x - width - caret < 0) {
  11013. return true;
  11014. }
  11015. }
  11016. function determineXAlign(chart, options, size, yAlign) {
  11017. const {x, width} = size;
  11018. const {width: chartWidth, chartArea: {left, right}} = chart;
  11019. let xAlign = 'center';
  11020. if (yAlign === 'center') {
  11021. xAlign = x <= (left + right) / 2 ? 'left' : 'right';
  11022. } else if (x <= width / 2) {
  11023. xAlign = 'left';
  11024. } else if (x >= chartWidth - width / 2) {
  11025. xAlign = 'right';
  11026. }
  11027. if (doesNotFitWithAlign(xAlign, chart, options, size)) {
  11028. xAlign = 'center';
  11029. }
  11030. return xAlign;
  11031. }
  11032. function determineAlignment(chart, options, size) {
  11033. const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
  11034. return {
  11035. xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
  11036. yAlign
  11037. };
  11038. }
  11039. function alignX(size, xAlign) {
  11040. let {x, width} = size;
  11041. if (xAlign === 'right') {
  11042. x -= width;
  11043. } else if (xAlign === 'center') {
  11044. x -= (width / 2);
  11045. }
  11046. return x;
  11047. }
  11048. function alignY(size, yAlign, paddingAndSize) {
  11049. let {y, height} = size;
  11050. if (yAlign === 'top') {
  11051. y += paddingAndSize;
  11052. } else if (yAlign === 'bottom') {
  11053. y -= height + paddingAndSize;
  11054. } else {
  11055. y -= (height / 2);
  11056. }
  11057. return y;
  11058. }
  11059. function getBackgroundPoint(options, size, alignment, chart) {
  11060. const {caretSize, caretPadding, cornerRadius} = options;
  11061. const {xAlign, yAlign} = alignment;
  11062. const paddingAndSize = caretSize + caretPadding;
  11063. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
  11064. let x = alignX(size, xAlign);
  11065. const y = alignY(size, yAlign, paddingAndSize);
  11066. if (yAlign === 'center') {
  11067. if (xAlign === 'left') {
  11068. x += paddingAndSize;
  11069. } else if (xAlign === 'right') {
  11070. x -= paddingAndSize;
  11071. }
  11072. } else if (xAlign === 'left') {
  11073. x -= Math.max(topLeft, bottomLeft) + caretSize;
  11074. } else if (xAlign === 'right') {
  11075. x += Math.max(topRight, bottomRight) + caretSize;
  11076. }
  11077. return {
  11078. x: _limitValue(x, 0, chart.width - size.width),
  11079. y: _limitValue(y, 0, chart.height - size.height)
  11080. };
  11081. }
  11082. function getAlignedX(tooltip, align, options) {
  11083. const padding = toPadding(options.padding);
  11084. return align === 'center'
  11085. ? tooltip.x + tooltip.width / 2
  11086. : align === 'right'
  11087. ? tooltip.x + tooltip.width - padding.right
  11088. : tooltip.x + padding.left;
  11089. }
  11090. function getBeforeAfterBodyLines(callback) {
  11091. return pushOrConcat([], splitNewlines(callback));
  11092. }
  11093. function createTooltipContext(parent, tooltip, tooltipItems) {
  11094. return createContext(parent, {
  11095. tooltip,
  11096. tooltipItems,
  11097. type: 'tooltip'
  11098. });
  11099. }
  11100. function overrideCallbacks(callbacks, context) {
  11101. const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
  11102. return override ? callbacks.override(override) : callbacks;
  11103. }
  11104. class Tooltip extends Element {
  11105. constructor(config) {
  11106. super();
  11107. this.opacity = 0;
  11108. this._active = [];
  11109. this._eventPosition = undefined;
  11110. this._size = undefined;
  11111. this._cachedAnimations = undefined;
  11112. this._tooltipItems = [];
  11113. this.$animations = undefined;
  11114. this.$context = undefined;
  11115. this.chart = config.chart || config._chart;
  11116. this._chart = this.chart;
  11117. this.options = config.options;
  11118. this.dataPoints = undefined;
  11119. this.title = undefined;
  11120. this.beforeBody = undefined;
  11121. this.body = undefined;
  11122. this.afterBody = undefined;
  11123. this.footer = undefined;
  11124. this.xAlign = undefined;
  11125. this.yAlign = undefined;
  11126. this.x = undefined;
  11127. this.y = undefined;
  11128. this.height = undefined;
  11129. this.width = undefined;
  11130. this.caretX = undefined;
  11131. this.caretY = undefined;
  11132. this.labelColors = undefined;
  11133. this.labelPointStyles = undefined;
  11134. this.labelTextColors = undefined;
  11135. }
  11136. initialize(options) {
  11137. this.options = options;
  11138. this._cachedAnimations = undefined;
  11139. this.$context = undefined;
  11140. }
  11141. _resolveAnimations() {
  11142. const cached = this._cachedAnimations;
  11143. if (cached) {
  11144. return cached;
  11145. }
  11146. const chart = this.chart;
  11147. const options = this.options.setContext(this.getContext());
  11148. const opts = options.enabled && chart.options.animation && options.animations;
  11149. const animations = new Animations(this.chart, opts);
  11150. if (opts._cacheable) {
  11151. this._cachedAnimations = Object.freeze(animations);
  11152. }
  11153. return animations;
  11154. }
  11155. getContext() {
  11156. return this.$context ||
  11157. (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
  11158. }
  11159. getTitle(context, options) {
  11160. const {callbacks} = options;
  11161. const beforeTitle = callbacks.beforeTitle.apply(this, [context]);
  11162. const title = callbacks.title.apply(this, [context]);
  11163. const afterTitle = callbacks.afterTitle.apply(this, [context]);
  11164. let lines = [];
  11165. lines = pushOrConcat(lines, splitNewlines(beforeTitle));
  11166. lines = pushOrConcat(lines, splitNewlines(title));
  11167. lines = pushOrConcat(lines, splitNewlines(afterTitle));
  11168. return lines;
  11169. }
  11170. getBeforeBody(tooltipItems, options) {
  11171. return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems]));
  11172. }
  11173. getBody(tooltipItems, options) {
  11174. const {callbacks} = options;
  11175. const bodyItems = [];
  11176. each(tooltipItems, (context) => {
  11177. const bodyItem = {
  11178. before: [],
  11179. lines: [],
  11180. after: []
  11181. };
  11182. const scoped = overrideCallbacks(callbacks, context);
  11183. pushOrConcat(bodyItem.before, splitNewlines(scoped.beforeLabel.call(this, context)));
  11184. pushOrConcat(bodyItem.lines, scoped.label.call(this, context));
  11185. pushOrConcat(bodyItem.after, splitNewlines(scoped.afterLabel.call(this, context)));
  11186. bodyItems.push(bodyItem);
  11187. });
  11188. return bodyItems;
  11189. }
  11190. getAfterBody(tooltipItems, options) {
  11191. return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems]));
  11192. }
  11193. getFooter(tooltipItems, options) {
  11194. const {callbacks} = options;
  11195. const beforeFooter = callbacks.beforeFooter.apply(this, [tooltipItems]);
  11196. const footer = callbacks.footer.apply(this, [tooltipItems]);
  11197. const afterFooter = callbacks.afterFooter.apply(this, [tooltipItems]);
  11198. let lines = [];
  11199. lines = pushOrConcat(lines, splitNewlines(beforeFooter));
  11200. lines = pushOrConcat(lines, splitNewlines(footer));
  11201. lines = pushOrConcat(lines, splitNewlines(afterFooter));
  11202. return lines;
  11203. }
  11204. _createItems(options) {
  11205. const active = this._active;
  11206. const data = this.chart.data;
  11207. const labelColors = [];
  11208. const labelPointStyles = [];
  11209. const labelTextColors = [];
  11210. let tooltipItems = [];
  11211. let i, len;
  11212. for (i = 0, len = active.length; i < len; ++i) {
  11213. tooltipItems.push(createTooltipItem(this.chart, active[i]));
  11214. }
  11215. if (options.filter) {
  11216. tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data));
  11217. }
  11218. if (options.itemSort) {
  11219. tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data));
  11220. }
  11221. each(tooltipItems, (context) => {
  11222. const scoped = overrideCallbacks(options.callbacks, context);
  11223. labelColors.push(scoped.labelColor.call(this, context));
  11224. labelPointStyles.push(scoped.labelPointStyle.call(this, context));
  11225. labelTextColors.push(scoped.labelTextColor.call(this, context));
  11226. });
  11227. this.labelColors = labelColors;
  11228. this.labelPointStyles = labelPointStyles;
  11229. this.labelTextColors = labelTextColors;
  11230. this.dataPoints = tooltipItems;
  11231. return tooltipItems;
  11232. }
  11233. update(changed, replay) {
  11234. const options = this.options.setContext(this.getContext());
  11235. const active = this._active;
  11236. let properties;
  11237. let tooltipItems = [];
  11238. if (!active.length) {
  11239. if (this.opacity !== 0) {
  11240. properties = {
  11241. opacity: 0
  11242. };
  11243. }
  11244. } else {
  11245. const position = positioners[options.position].call(this, active, this._eventPosition);
  11246. tooltipItems = this._createItems(options);
  11247. this.title = this.getTitle(tooltipItems, options);
  11248. this.beforeBody = this.getBeforeBody(tooltipItems, options);
  11249. this.body = this.getBody(tooltipItems, options);
  11250. this.afterBody = this.getAfterBody(tooltipItems, options);
  11251. this.footer = this.getFooter(tooltipItems, options);
  11252. const size = this._size = getTooltipSize(this, options);
  11253. const positionAndSize = Object.assign({}, position, size);
  11254. const alignment = determineAlignment(this.chart, options, positionAndSize);
  11255. const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
  11256. this.xAlign = alignment.xAlign;
  11257. this.yAlign = alignment.yAlign;
  11258. properties = {
  11259. opacity: 1,
  11260. x: backgroundPoint.x,
  11261. y: backgroundPoint.y,
  11262. width: size.width,
  11263. height: size.height,
  11264. caretX: position.x,
  11265. caretY: position.y
  11266. };
  11267. }
  11268. this._tooltipItems = tooltipItems;
  11269. this.$context = undefined;
  11270. if (properties) {
  11271. this._resolveAnimations().update(this, properties);
  11272. }
  11273. if (changed && options.external) {
  11274. options.external.call(this, {chart: this.chart, tooltip: this, replay});
  11275. }
  11276. }
  11277. drawCaret(tooltipPoint, ctx, size, options) {
  11278. const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
  11279. ctx.lineTo(caretPosition.x1, caretPosition.y1);
  11280. ctx.lineTo(caretPosition.x2, caretPosition.y2);
  11281. ctx.lineTo(caretPosition.x3, caretPosition.y3);
  11282. }
  11283. getCaretPosition(tooltipPoint, size, options) {
  11284. const {xAlign, yAlign} = this;
  11285. const {caretSize, cornerRadius} = options;
  11286. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);
  11287. const {x: ptX, y: ptY} = tooltipPoint;
  11288. const {width, height} = size;
  11289. let x1, x2, x3, y1, y2, y3;
  11290. if (yAlign === 'center') {
  11291. y2 = ptY + (height / 2);
  11292. if (xAlign === 'left') {
  11293. x1 = ptX;
  11294. x2 = x1 - caretSize;
  11295. y1 = y2 + caretSize;
  11296. y3 = y2 - caretSize;
  11297. } else {
  11298. x1 = ptX + width;
  11299. x2 = x1 + caretSize;
  11300. y1 = y2 - caretSize;
  11301. y3 = y2 + caretSize;
  11302. }
  11303. x3 = x1;
  11304. } else {
  11305. if (xAlign === 'left') {
  11306. x2 = ptX + Math.max(topLeft, bottomLeft) + (caretSize);
  11307. } else if (xAlign === 'right') {
  11308. x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
  11309. } else {
  11310. x2 = this.caretX;
  11311. }
  11312. if (yAlign === 'top') {
  11313. y1 = ptY;
  11314. y2 = y1 - caretSize;
  11315. x1 = x2 - caretSize;
  11316. x3 = x2 + caretSize;
  11317. } else {
  11318. y1 = ptY + height;
  11319. y2 = y1 + caretSize;
  11320. x1 = x2 + caretSize;
  11321. x3 = x2 - caretSize;
  11322. }
  11323. y3 = y1;
  11324. }
  11325. return {x1, x2, x3, y1, y2, y3};
  11326. }
  11327. drawTitle(pt, ctx, options) {
  11328. const title = this.title;
  11329. const length = title.length;
  11330. let titleFont, titleSpacing, i;
  11331. if (length) {
  11332. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11333. pt.x = getAlignedX(this, options.titleAlign, options);
  11334. ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
  11335. ctx.textBaseline = 'middle';
  11336. titleFont = toFont(options.titleFont);
  11337. titleSpacing = options.titleSpacing;
  11338. ctx.fillStyle = options.titleColor;
  11339. ctx.font = titleFont.string;
  11340. for (i = 0; i < length; ++i) {
  11341. ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
  11342. pt.y += titleFont.lineHeight + titleSpacing;
  11343. if (i + 1 === length) {
  11344. pt.y += options.titleMarginBottom - titleSpacing;
  11345. }
  11346. }
  11347. }
  11348. }
  11349. _drawColorBox(ctx, pt, i, rtlHelper, options) {
  11350. const labelColors = this.labelColors[i];
  11351. const labelPointStyle = this.labelPointStyles[i];
  11352. const {boxHeight, boxWidth, boxPadding} = options;
  11353. const bodyFont = toFont(options.bodyFont);
  11354. const colorX = getAlignedX(this, 'left', options);
  11355. const rtlColorX = rtlHelper.x(colorX);
  11356. const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
  11357. const colorY = pt.y + yOffSet;
  11358. if (options.usePointStyle) {
  11359. const drawOptions = {
  11360. radius: Math.min(boxWidth, boxHeight) / 2,
  11361. pointStyle: labelPointStyle.pointStyle,
  11362. rotation: labelPointStyle.rotation,
  11363. borderWidth: 1
  11364. };
  11365. const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
  11366. const centerY = colorY + boxHeight / 2;
  11367. ctx.strokeStyle = options.multiKeyBackground;
  11368. ctx.fillStyle = options.multiKeyBackground;
  11369. drawPoint(ctx, drawOptions, centerX, centerY);
  11370. ctx.strokeStyle = labelColors.borderColor;
  11371. ctx.fillStyle = labelColors.backgroundColor;
  11372. drawPoint(ctx, drawOptions, centerX, centerY);
  11373. } else {
  11374. ctx.lineWidth = labelColors.borderWidth || 1;
  11375. ctx.strokeStyle = labelColors.borderColor;
  11376. ctx.setLineDash(labelColors.borderDash || []);
  11377. ctx.lineDashOffset = labelColors.borderDashOffset || 0;
  11378. const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth - boxPadding);
  11379. const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - boxPadding - 2);
  11380. const borderRadius = toTRBLCorners(labelColors.borderRadius);
  11381. if (Object.values(borderRadius).some(v => v !== 0)) {
  11382. ctx.beginPath();
  11383. ctx.fillStyle = options.multiKeyBackground;
  11384. addRoundedRectPath(ctx, {
  11385. x: outerX,
  11386. y: colorY,
  11387. w: boxWidth,
  11388. h: boxHeight,
  11389. radius: borderRadius,
  11390. });
  11391. ctx.fill();
  11392. ctx.stroke();
  11393. ctx.fillStyle = labelColors.backgroundColor;
  11394. ctx.beginPath();
  11395. addRoundedRectPath(ctx, {
  11396. x: innerX,
  11397. y: colorY + 1,
  11398. w: boxWidth - 2,
  11399. h: boxHeight - 2,
  11400. radius: borderRadius,
  11401. });
  11402. ctx.fill();
  11403. } else {
  11404. ctx.fillStyle = options.multiKeyBackground;
  11405. ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
  11406. ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
  11407. ctx.fillStyle = labelColors.backgroundColor;
  11408. ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
  11409. }
  11410. }
  11411. ctx.fillStyle = this.labelTextColors[i];
  11412. }
  11413. drawBody(pt, ctx, options) {
  11414. const {body} = this;
  11415. const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth, boxPadding} = options;
  11416. const bodyFont = toFont(options.bodyFont);
  11417. let bodyLineHeight = bodyFont.lineHeight;
  11418. let xLinePadding = 0;
  11419. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11420. const fillLineOfText = function(line) {
  11421. ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
  11422. pt.y += bodyLineHeight + bodySpacing;
  11423. };
  11424. const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
  11425. let bodyItem, textColor, lines, i, j, ilen, jlen;
  11426. ctx.textAlign = bodyAlign;
  11427. ctx.textBaseline = 'middle';
  11428. ctx.font = bodyFont.string;
  11429. pt.x = getAlignedX(this, bodyAlignForCalculation, options);
  11430. ctx.fillStyle = options.bodyColor;
  11431. each(this.beforeBody, fillLineOfText);
  11432. xLinePadding = displayColors && bodyAlignForCalculation !== 'right'
  11433. ? bodyAlign === 'center' ? (boxWidth / 2 + boxPadding) : (boxWidth + 2 + boxPadding)
  11434. : 0;
  11435. for (i = 0, ilen = body.length; i < ilen; ++i) {
  11436. bodyItem = body[i];
  11437. textColor = this.labelTextColors[i];
  11438. ctx.fillStyle = textColor;
  11439. each(bodyItem.before, fillLineOfText);
  11440. lines = bodyItem.lines;
  11441. if (displayColors && lines.length) {
  11442. this._drawColorBox(ctx, pt, i, rtlHelper, options);
  11443. bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
  11444. }
  11445. for (j = 0, jlen = lines.length; j < jlen; ++j) {
  11446. fillLineOfText(lines[j]);
  11447. bodyLineHeight = bodyFont.lineHeight;
  11448. }
  11449. each(bodyItem.after, fillLineOfText);
  11450. }
  11451. xLinePadding = 0;
  11452. bodyLineHeight = bodyFont.lineHeight;
  11453. each(this.afterBody, fillLineOfText);
  11454. pt.y -= bodySpacing;
  11455. }
  11456. drawFooter(pt, ctx, options) {
  11457. const footer = this.footer;
  11458. const length = footer.length;
  11459. let footerFont, i;
  11460. if (length) {
  11461. const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
  11462. pt.x = getAlignedX(this, options.footerAlign, options);
  11463. pt.y += options.footerMarginTop;
  11464. ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
  11465. ctx.textBaseline = 'middle';
  11466. footerFont = toFont(options.footerFont);
  11467. ctx.fillStyle = options.footerColor;
  11468. ctx.font = footerFont.string;
  11469. for (i = 0; i < length; ++i) {
  11470. ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
  11471. pt.y += footerFont.lineHeight + options.footerSpacing;
  11472. }
  11473. }
  11474. }
  11475. drawBackground(pt, ctx, tooltipSize, options) {
  11476. const {xAlign, yAlign} = this;
  11477. const {x, y} = pt;
  11478. const {width, height} = tooltipSize;
  11479. const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(options.cornerRadius);
  11480. ctx.fillStyle = options.backgroundColor;
  11481. ctx.strokeStyle = options.borderColor;
  11482. ctx.lineWidth = options.borderWidth;
  11483. ctx.beginPath();
  11484. ctx.moveTo(x + topLeft, y);
  11485. if (yAlign === 'top') {
  11486. this.drawCaret(pt, ctx, tooltipSize, options);
  11487. }
  11488. ctx.lineTo(x + width - topRight, y);
  11489. ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
  11490. if (yAlign === 'center' && xAlign === 'right') {
  11491. this.drawCaret(pt, ctx, tooltipSize, options);
  11492. }
  11493. ctx.lineTo(x + width, y + height - bottomRight);
  11494. ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
  11495. if (yAlign === 'bottom') {
  11496. this.drawCaret(pt, ctx, tooltipSize, options);
  11497. }
  11498. ctx.lineTo(x + bottomLeft, y + height);
  11499. ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
  11500. if (yAlign === 'center' && xAlign === 'left') {
  11501. this.drawCaret(pt, ctx, tooltipSize, options);
  11502. }
  11503. ctx.lineTo(x, y + topLeft);
  11504. ctx.quadraticCurveTo(x, y, x + topLeft, y);
  11505. ctx.closePath();
  11506. ctx.fill();
  11507. if (options.borderWidth > 0) {
  11508. ctx.stroke();
  11509. }
  11510. }
  11511. _updateAnimationTarget(options) {
  11512. const chart = this.chart;
  11513. const anims = this.$animations;
  11514. const animX = anims && anims.x;
  11515. const animY = anims && anims.y;
  11516. if (animX || animY) {
  11517. const position = positioners[options.position].call(this, this._active, this._eventPosition);
  11518. if (!position) {
  11519. return;
  11520. }
  11521. const size = this._size = getTooltipSize(this, options);
  11522. const positionAndSize = Object.assign({}, position, this._size);
  11523. const alignment = determineAlignment(chart, options, positionAndSize);
  11524. const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
  11525. if (animX._to !== point.x || animY._to !== point.y) {
  11526. this.xAlign = alignment.xAlign;
  11527. this.yAlign = alignment.yAlign;
  11528. this.width = size.width;
  11529. this.height = size.height;
  11530. this.caretX = position.x;
  11531. this.caretY = position.y;
  11532. this._resolveAnimations().update(this, point);
  11533. }
  11534. }
  11535. }
  11536. draw(ctx) {
  11537. const options = this.options.setContext(this.getContext());
  11538. let opacity = this.opacity;
  11539. if (!opacity) {
  11540. return;
  11541. }
  11542. this._updateAnimationTarget(options);
  11543. const tooltipSize = {
  11544. width: this.width,
  11545. height: this.height
  11546. };
  11547. const pt = {
  11548. x: this.x,
  11549. y: this.y
  11550. };
  11551. opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
  11552. const padding = toPadding(options.padding);
  11553. const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
  11554. if (options.enabled && hasTooltipContent) {
  11555. ctx.save();
  11556. ctx.globalAlpha = opacity;
  11557. this.drawBackground(pt, ctx, tooltipSize, options);
  11558. overrideTextDirection(ctx, options.textDirection);
  11559. pt.y += padding.top;
  11560. this.drawTitle(pt, ctx, options);
  11561. this.drawBody(pt, ctx, options);
  11562. this.drawFooter(pt, ctx, options);
  11563. restoreTextDirection(ctx, options.textDirection);
  11564. ctx.restore();
  11565. }
  11566. }
  11567. getActiveElements() {
  11568. return this._active || [];
  11569. }
  11570. setActiveElements(activeElements, eventPosition) {
  11571. const lastActive = this._active;
  11572. const active = activeElements.map(({datasetIndex, index}) => {
  11573. const meta = this.chart.getDatasetMeta(datasetIndex);
  11574. if (!meta) {
  11575. throw new Error('Cannot find a dataset at index ' + datasetIndex);
  11576. }
  11577. return {
  11578. datasetIndex,
  11579. element: meta.data[index],
  11580. index,
  11581. };
  11582. });
  11583. const changed = !_elementsEqual(lastActive, active);
  11584. const positionChanged = this._positionChanged(active, eventPosition);
  11585. if (changed || positionChanged) {
  11586. this._active = active;
  11587. this._eventPosition = eventPosition;
  11588. this._ignoreReplayEvents = true;
  11589. this.update(true);
  11590. }
  11591. }
  11592. handleEvent(e, replay, inChartArea = true) {
  11593. if (replay && this._ignoreReplayEvents) {
  11594. return false;
  11595. }
  11596. this._ignoreReplayEvents = false;
  11597. const options = this.options;
  11598. const lastActive = this._active || [];
  11599. const active = this._getActiveElements(e, lastActive, replay, inChartArea);
  11600. const positionChanged = this._positionChanged(active, e);
  11601. const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
  11602. if (changed) {
  11603. this._active = active;
  11604. if (options.enabled || options.external) {
  11605. this._eventPosition = {
  11606. x: e.x,
  11607. y: e.y
  11608. };
  11609. this.update(true, replay);
  11610. }
  11611. }
  11612. return changed;
  11613. }
  11614. _getActiveElements(e, lastActive, replay, inChartArea) {
  11615. const options = this.options;
  11616. if (e.type === 'mouseout') {
  11617. return [];
  11618. }
  11619. if (!inChartArea) {
  11620. return lastActive;
  11621. }
  11622. const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
  11623. if (options.reverse) {
  11624. active.reverse();
  11625. }
  11626. return active;
  11627. }
  11628. _positionChanged(active, e) {
  11629. const {caretX, caretY, options} = this;
  11630. const position = positioners[options.position].call(this, active, e);
  11631. return position !== false && (caretX !== position.x || caretY !== position.y);
  11632. }
  11633. }
  11634. Tooltip.positioners = positioners;
  11635. var plugin_tooltip = {
  11636. id: 'tooltip',
  11637. _element: Tooltip,
  11638. positioners,
  11639. afterInit(chart, _args, options) {
  11640. if (options) {
  11641. chart.tooltip = new Tooltip({chart, options});
  11642. }
  11643. },
  11644. beforeUpdate(chart, _args, options) {
  11645. if (chart.tooltip) {
  11646. chart.tooltip.initialize(options);
  11647. }
  11648. },
  11649. reset(chart, _args, options) {
  11650. if (chart.tooltip) {
  11651. chart.tooltip.initialize(options);
  11652. }
  11653. },
  11654. afterDraw(chart) {
  11655. const tooltip = chart.tooltip;
  11656. const args = {
  11657. tooltip
  11658. };
  11659. if (chart.notifyPlugins('beforeTooltipDraw', args) === false) {
  11660. return;
  11661. }
  11662. if (tooltip) {
  11663. tooltip.draw(chart.ctx);
  11664. }
  11665. chart.notifyPlugins('afterTooltipDraw', args);
  11666. },
  11667. afterEvent(chart, args) {
  11668. if (chart.tooltip) {
  11669. const useFinalPosition = args.replay;
  11670. if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
  11671. args.changed = true;
  11672. }
  11673. }
  11674. },
  11675. defaults: {
  11676. enabled: true,
  11677. external: null,
  11678. position: 'average',
  11679. backgroundColor: 'rgba(0,0,0,0.8)',
  11680. titleColor: '#fff',
  11681. titleFont: {
  11682. weight: 'bold',
  11683. },
  11684. titleSpacing: 2,
  11685. titleMarginBottom: 6,
  11686. titleAlign: 'left',
  11687. bodyColor: '#fff',
  11688. bodySpacing: 2,
  11689. bodyFont: {
  11690. },
  11691. bodyAlign: 'left',
  11692. footerColor: '#fff',
  11693. footerSpacing: 2,
  11694. footerMarginTop: 6,
  11695. footerFont: {
  11696. weight: 'bold',
  11697. },
  11698. footerAlign: 'left',
  11699. padding: 6,
  11700. caretPadding: 2,
  11701. caretSize: 5,
  11702. cornerRadius: 6,
  11703. boxHeight: (ctx, opts) => opts.bodyFont.size,
  11704. boxWidth: (ctx, opts) => opts.bodyFont.size,
  11705. multiKeyBackground: '#fff',
  11706. displayColors: true,
  11707. boxPadding: 0,
  11708. borderColor: 'rgba(0,0,0,0)',
  11709. borderWidth: 0,
  11710. animation: {
  11711. duration: 400,
  11712. easing: 'easeOutQuart',
  11713. },
  11714. animations: {
  11715. numbers: {
  11716. type: 'number',
  11717. properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
  11718. },
  11719. opacity: {
  11720. easing: 'linear',
  11721. duration: 200
  11722. }
  11723. },
  11724. callbacks: {
  11725. beforeTitle: noop,
  11726. title(tooltipItems) {
  11727. if (tooltipItems.length > 0) {
  11728. const item = tooltipItems[0];
  11729. const labels = item.chart.data.labels;
  11730. const labelCount = labels ? labels.length : 0;
  11731. if (this && this.options && this.options.mode === 'dataset') {
  11732. return item.dataset.label || '';
  11733. } else if (item.label) {
  11734. return item.label;
  11735. } else if (labelCount > 0 && item.dataIndex < labelCount) {
  11736. return labels[item.dataIndex];
  11737. }
  11738. }
  11739. return '';
  11740. },
  11741. afterTitle: noop,
  11742. beforeBody: noop,
  11743. beforeLabel: noop,
  11744. label(tooltipItem) {
  11745. if (this && this.options && this.options.mode === 'dataset') {
  11746. return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
  11747. }
  11748. let label = tooltipItem.dataset.label || '';
  11749. if (label) {
  11750. label += ': ';
  11751. }
  11752. const value = tooltipItem.formattedValue;
  11753. if (!isNullOrUndef(value)) {
  11754. label += value;
  11755. }
  11756. return label;
  11757. },
  11758. labelColor(tooltipItem) {
  11759. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  11760. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  11761. return {
  11762. borderColor: options.borderColor,
  11763. backgroundColor: options.backgroundColor,
  11764. borderWidth: options.borderWidth,
  11765. borderDash: options.borderDash,
  11766. borderDashOffset: options.borderDashOffset,
  11767. borderRadius: 0,
  11768. };
  11769. },
  11770. labelTextColor() {
  11771. return this.options.bodyColor;
  11772. },
  11773. labelPointStyle(tooltipItem) {
  11774. const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
  11775. const options = meta.controller.getStyle(tooltipItem.dataIndex);
  11776. return {
  11777. pointStyle: options.pointStyle,
  11778. rotation: options.rotation,
  11779. };
  11780. },
  11781. afterLabel: noop,
  11782. afterBody: noop,
  11783. beforeFooter: noop,
  11784. footer: noop,
  11785. afterFooter: noop
  11786. }
  11787. },
  11788. defaultRoutes: {
  11789. bodyFont: 'font',
  11790. footerFont: 'font',
  11791. titleFont: 'font'
  11792. },
  11793. descriptors: {
  11794. _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external',
  11795. _indexable: false,
  11796. callbacks: {
  11797. _scriptable: false,
  11798. _indexable: false,
  11799. },
  11800. animation: {
  11801. _fallback: false
  11802. },
  11803. animations: {
  11804. _fallback: 'animation'
  11805. }
  11806. },
  11807. additionalOptionScopes: ['interaction']
  11808. };
  11809. var plugins = /*#__PURE__*/Object.freeze({
  11810. __proto__: null,
  11811. Decimation: plugin_decimation,
  11812. Filler: index,
  11813. Legend: plugin_legend,
  11814. SubTitle: plugin_subtitle,
  11815. Title: plugin_title,
  11816. Tooltip: plugin_tooltip
  11817. });
  11818. const addIfString = (labels, raw, index, addedLabels) => {
  11819. if (typeof raw === 'string') {
  11820. index = labels.push(raw) - 1;
  11821. addedLabels.unshift({index, label: raw});
  11822. } else if (isNaN(raw)) {
  11823. index = null;
  11824. }
  11825. return index;
  11826. };
  11827. function findOrAddLabel(labels, raw, index, addedLabels) {
  11828. const first = labels.indexOf(raw);
  11829. if (first === -1) {
  11830. return addIfString(labels, raw, index, addedLabels);
  11831. }
  11832. const last = labels.lastIndexOf(raw);
  11833. return first !== last ? index : first;
  11834. }
  11835. const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);
  11836. class CategoryScale extends Scale {
  11837. constructor(cfg) {
  11838. super(cfg);
  11839. this._startValue = undefined;
  11840. this._valueRange = 0;
  11841. this._addedLabels = [];
  11842. }
  11843. init(scaleOptions) {
  11844. const added = this._addedLabels;
  11845. if (added.length) {
  11846. const labels = this.getLabels();
  11847. for (const {index, label} of added) {
  11848. if (labels[index] === label) {
  11849. labels.splice(index, 1);
  11850. }
  11851. }
  11852. this._addedLabels = [];
  11853. }
  11854. super.init(scaleOptions);
  11855. }
  11856. parse(raw, index) {
  11857. if (isNullOrUndef(raw)) {
  11858. return null;
  11859. }
  11860. const labels = this.getLabels();
  11861. index = isFinite(index) && labels[index] === raw ? index
  11862. : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
  11863. return validIndex(index, labels.length - 1);
  11864. }
  11865. determineDataLimits() {
  11866. const {minDefined, maxDefined} = this.getUserBounds();
  11867. let {min, max} = this.getMinMax(true);
  11868. if (this.options.bounds === 'ticks') {
  11869. if (!minDefined) {
  11870. min = 0;
  11871. }
  11872. if (!maxDefined) {
  11873. max = this.getLabels().length - 1;
  11874. }
  11875. }
  11876. this.min = min;
  11877. this.max = max;
  11878. }
  11879. buildTicks() {
  11880. const min = this.min;
  11881. const max = this.max;
  11882. const offset = this.options.offset;
  11883. const ticks = [];
  11884. let labels = this.getLabels();
  11885. labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1);
  11886. this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
  11887. this._startValue = this.min - (offset ? 0.5 : 0);
  11888. for (let value = min; value <= max; value++) {
  11889. ticks.push({value});
  11890. }
  11891. return ticks;
  11892. }
  11893. getLabelForValue(value) {
  11894. const labels = this.getLabels();
  11895. if (value >= 0 && value < labels.length) {
  11896. return labels[value];
  11897. }
  11898. return value;
  11899. }
  11900. configure() {
  11901. super.configure();
  11902. if (!this.isHorizontal()) {
  11903. this._reversePixels = !this._reversePixels;
  11904. }
  11905. }
  11906. getPixelForValue(value) {
  11907. if (typeof value !== 'number') {
  11908. value = this.parse(value);
  11909. }
  11910. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  11911. }
  11912. getPixelForTick(index) {
  11913. const ticks = this.ticks;
  11914. if (index < 0 || index > ticks.length - 1) {
  11915. return null;
  11916. }
  11917. return this.getPixelForValue(ticks[index].value);
  11918. }
  11919. getValueForPixel(pixel) {
  11920. return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
  11921. }
  11922. getBasePixel() {
  11923. return this.bottom;
  11924. }
  11925. }
  11926. CategoryScale.id = 'category';
  11927. CategoryScale.defaults = {
  11928. ticks: {
  11929. callback: CategoryScale.prototype.getLabelForValue
  11930. }
  11931. };
  11932. function generateTicks$1(generationOptions, dataRange) {
  11933. const ticks = [];
  11934. const MIN_SPACING = 1e-14;
  11935. const {bounds, step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions;
  11936. const unit = step || 1;
  11937. const maxSpaces = maxTicks - 1;
  11938. const {min: rmin, max: rmax} = dataRange;
  11939. const minDefined = !isNullOrUndef(min);
  11940. const maxDefined = !isNullOrUndef(max);
  11941. const countDefined = !isNullOrUndef(count);
  11942. const minSpacing = (rmax - rmin) / (maxDigits + 1);
  11943. let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
  11944. let factor, niceMin, niceMax, numSpaces;
  11945. if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
  11946. return [{value: rmin}, {value: rmax}];
  11947. }
  11948. numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
  11949. if (numSpaces > maxSpaces) {
  11950. spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
  11951. }
  11952. if (!isNullOrUndef(precision)) {
  11953. factor = Math.pow(10, precision);
  11954. spacing = Math.ceil(spacing * factor) / factor;
  11955. }
  11956. if (bounds === 'ticks') {
  11957. niceMin = Math.floor(rmin / spacing) * spacing;
  11958. niceMax = Math.ceil(rmax / spacing) * spacing;
  11959. } else {
  11960. niceMin = rmin;
  11961. niceMax = rmax;
  11962. }
  11963. if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
  11964. numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
  11965. spacing = (max - min) / numSpaces;
  11966. niceMin = min;
  11967. niceMax = max;
  11968. } else if (countDefined) {
  11969. niceMin = minDefined ? min : niceMin;
  11970. niceMax = maxDefined ? max : niceMax;
  11971. numSpaces = count - 1;
  11972. spacing = (niceMax - niceMin) / numSpaces;
  11973. } else {
  11974. numSpaces = (niceMax - niceMin) / spacing;
  11975. if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
  11976. numSpaces = Math.round(numSpaces);
  11977. } else {
  11978. numSpaces = Math.ceil(numSpaces);
  11979. }
  11980. }
  11981. const decimalPlaces = Math.max(
  11982. _decimalPlaces(spacing),
  11983. _decimalPlaces(niceMin)
  11984. );
  11985. factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
  11986. niceMin = Math.round(niceMin * factor) / factor;
  11987. niceMax = Math.round(niceMax * factor) / factor;
  11988. let j = 0;
  11989. if (minDefined) {
  11990. if (includeBounds && niceMin !== min) {
  11991. ticks.push({value: min});
  11992. if (niceMin < min) {
  11993. j++;
  11994. }
  11995. if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
  11996. j++;
  11997. }
  11998. } else if (niceMin < min) {
  11999. j++;
  12000. }
  12001. }
  12002. for (; j < numSpaces; ++j) {
  12003. ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor});
  12004. }
  12005. if (maxDefined && includeBounds && niceMax !== max) {
  12006. if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
  12007. ticks[ticks.length - 1].value = max;
  12008. } else {
  12009. ticks.push({value: max});
  12010. }
  12011. } else if (!maxDefined || niceMax === max) {
  12012. ticks.push({value: niceMax});
  12013. }
  12014. return ticks;
  12015. }
  12016. function relativeLabelSize(value, minSpacing, {horizontal, minRotation}) {
  12017. const rad = toRadians(minRotation);
  12018. const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
  12019. const length = 0.75 * minSpacing * ('' + value).length;
  12020. return Math.min(minSpacing / ratio, length);
  12021. }
  12022. class LinearScaleBase extends Scale {
  12023. constructor(cfg) {
  12024. super(cfg);
  12025. this.start = undefined;
  12026. this.end = undefined;
  12027. this._startValue = undefined;
  12028. this._endValue = undefined;
  12029. this._valueRange = 0;
  12030. }
  12031. parse(raw, index) {
  12032. if (isNullOrUndef(raw)) {
  12033. return null;
  12034. }
  12035. if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
  12036. return null;
  12037. }
  12038. return +raw;
  12039. }
  12040. handleTickRangeOptions() {
  12041. const {beginAtZero} = this.options;
  12042. const {minDefined, maxDefined} = this.getUserBounds();
  12043. let {min, max} = this;
  12044. const setMin = v => (min = minDefined ? min : v);
  12045. const setMax = v => (max = maxDefined ? max : v);
  12046. if (beginAtZero) {
  12047. const minSign = sign(min);
  12048. const maxSign = sign(max);
  12049. if (minSign < 0 && maxSign < 0) {
  12050. setMax(0);
  12051. } else if (minSign > 0 && maxSign > 0) {
  12052. setMin(0);
  12053. }
  12054. }
  12055. if (min === max) {
  12056. let offset = 1;
  12057. if (max >= Number.MAX_SAFE_INTEGER || min <= Number.MIN_SAFE_INTEGER) {
  12058. offset = Math.abs(max * 0.05);
  12059. }
  12060. setMax(max + offset);
  12061. if (!beginAtZero) {
  12062. setMin(min - offset);
  12063. }
  12064. }
  12065. this.min = min;
  12066. this.max = max;
  12067. }
  12068. getTickLimit() {
  12069. const tickOpts = this.options.ticks;
  12070. let {maxTicksLimit, stepSize} = tickOpts;
  12071. let maxTicks;
  12072. if (stepSize) {
  12073. maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
  12074. if (maxTicks > 1000) {
  12075. console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
  12076. maxTicks = 1000;
  12077. }
  12078. } else {
  12079. maxTicks = this.computeTickLimit();
  12080. maxTicksLimit = maxTicksLimit || 11;
  12081. }
  12082. if (maxTicksLimit) {
  12083. maxTicks = Math.min(maxTicksLimit, maxTicks);
  12084. }
  12085. return maxTicks;
  12086. }
  12087. computeTickLimit() {
  12088. return Number.POSITIVE_INFINITY;
  12089. }
  12090. buildTicks() {
  12091. const opts = this.options;
  12092. const tickOpts = opts.ticks;
  12093. let maxTicks = this.getTickLimit();
  12094. maxTicks = Math.max(2, maxTicks);
  12095. const numericGeneratorOptions = {
  12096. maxTicks,
  12097. bounds: opts.bounds,
  12098. min: opts.min,
  12099. max: opts.max,
  12100. precision: tickOpts.precision,
  12101. step: tickOpts.stepSize,
  12102. count: tickOpts.count,
  12103. maxDigits: this._maxDigits(),
  12104. horizontal: this.isHorizontal(),
  12105. minRotation: tickOpts.minRotation || 0,
  12106. includeBounds: tickOpts.includeBounds !== false
  12107. };
  12108. const dataRange = this._range || this;
  12109. const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
  12110. if (opts.bounds === 'ticks') {
  12111. _setMinAndMaxByKey(ticks, this, 'value');
  12112. }
  12113. if (opts.reverse) {
  12114. ticks.reverse();
  12115. this.start = this.max;
  12116. this.end = this.min;
  12117. } else {
  12118. this.start = this.min;
  12119. this.end = this.max;
  12120. }
  12121. return ticks;
  12122. }
  12123. configure() {
  12124. const ticks = this.ticks;
  12125. let start = this.min;
  12126. let end = this.max;
  12127. super.configure();
  12128. if (this.options.offset && ticks.length) {
  12129. const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
  12130. start -= offset;
  12131. end += offset;
  12132. }
  12133. this._startValue = start;
  12134. this._endValue = end;
  12135. this._valueRange = end - start;
  12136. }
  12137. getLabelForValue(value) {
  12138. return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  12139. }
  12140. }
  12141. class LinearScale extends LinearScaleBase {
  12142. determineDataLimits() {
  12143. const {min, max} = this.getMinMax(true);
  12144. this.min = isNumberFinite(min) ? min : 0;
  12145. this.max = isNumberFinite(max) ? max : 1;
  12146. this.handleTickRangeOptions();
  12147. }
  12148. computeTickLimit() {
  12149. const horizontal = this.isHorizontal();
  12150. const length = horizontal ? this.width : this.height;
  12151. const minRotation = toRadians(this.options.ticks.minRotation);
  12152. const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
  12153. const tickFont = this._resolveTickFontOptions(0);
  12154. return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
  12155. }
  12156. getPixelForValue(value) {
  12157. return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
  12158. }
  12159. getValueForPixel(pixel) {
  12160. return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
  12161. }
  12162. }
  12163. LinearScale.id = 'linear';
  12164. LinearScale.defaults = {
  12165. ticks: {
  12166. callback: Ticks.formatters.numeric
  12167. }
  12168. };
  12169. function isMajor(tickVal) {
  12170. const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal))));
  12171. return remain === 1;
  12172. }
  12173. function generateTicks(generationOptions, dataRange) {
  12174. const endExp = Math.floor(log10(dataRange.max));
  12175. const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
  12176. const ticks = [];
  12177. let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
  12178. let exp = Math.floor(log10(tickVal));
  12179. let significand = Math.floor(tickVal / Math.pow(10, exp));
  12180. let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
  12181. do {
  12182. ticks.push({value: tickVal, major: isMajor(tickVal)});
  12183. ++significand;
  12184. if (significand === 10) {
  12185. significand = 1;
  12186. ++exp;
  12187. precision = exp >= 0 ? 1 : precision;
  12188. }
  12189. tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
  12190. } while (exp < endExp || (exp === endExp && significand < endSignificand));
  12191. const lastTick = finiteOrDefault(generationOptions.max, tickVal);
  12192. ticks.push({value: lastTick, major: isMajor(tickVal)});
  12193. return ticks;
  12194. }
  12195. class LogarithmicScale extends Scale {
  12196. constructor(cfg) {
  12197. super(cfg);
  12198. this.start = undefined;
  12199. this.end = undefined;
  12200. this._startValue = undefined;
  12201. this._valueRange = 0;
  12202. }
  12203. parse(raw, index) {
  12204. const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);
  12205. if (value === 0) {
  12206. this._zero = true;
  12207. return undefined;
  12208. }
  12209. return isNumberFinite(value) && value > 0 ? value : null;
  12210. }
  12211. determineDataLimits() {
  12212. const {min, max} = this.getMinMax(true);
  12213. this.min = isNumberFinite(min) ? Math.max(0, min) : null;
  12214. this.max = isNumberFinite(max) ? Math.max(0, max) : null;
  12215. if (this.options.beginAtZero) {
  12216. this._zero = true;
  12217. }
  12218. this.handleTickRangeOptions();
  12219. }
  12220. handleTickRangeOptions() {
  12221. const {minDefined, maxDefined} = this.getUserBounds();
  12222. let min = this.min;
  12223. let max = this.max;
  12224. const setMin = v => (min = minDefined ? min : v);
  12225. const setMax = v => (max = maxDefined ? max : v);
  12226. const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m);
  12227. if (min === max) {
  12228. if (min <= 0) {
  12229. setMin(1);
  12230. setMax(10);
  12231. } else {
  12232. setMin(exp(min, -1));
  12233. setMax(exp(max, +1));
  12234. }
  12235. }
  12236. if (min <= 0) {
  12237. setMin(exp(max, -1));
  12238. }
  12239. if (max <= 0) {
  12240. setMax(exp(min, +1));
  12241. }
  12242. if (this._zero && this.min !== this._suggestedMin && min === exp(this.min, 0)) {
  12243. setMin(exp(min, -1));
  12244. }
  12245. this.min = min;
  12246. this.max = max;
  12247. }
  12248. buildTicks() {
  12249. const opts = this.options;
  12250. const generationOptions = {
  12251. min: this._userMin,
  12252. max: this._userMax
  12253. };
  12254. const ticks = generateTicks(generationOptions, this);
  12255. if (opts.bounds === 'ticks') {
  12256. _setMinAndMaxByKey(ticks, this, 'value');
  12257. }
  12258. if (opts.reverse) {
  12259. ticks.reverse();
  12260. this.start = this.max;
  12261. this.end = this.min;
  12262. } else {
  12263. this.start = this.min;
  12264. this.end = this.max;
  12265. }
  12266. return ticks;
  12267. }
  12268. getLabelForValue(value) {
  12269. return value === undefined
  12270. ? '0'
  12271. : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
  12272. }
  12273. configure() {
  12274. const start = this.min;
  12275. super.configure();
  12276. this._startValue = log10(start);
  12277. this._valueRange = log10(this.max) - log10(start);
  12278. }
  12279. getPixelForValue(value) {
  12280. if (value === undefined || value === 0) {
  12281. value = this.min;
  12282. }
  12283. if (value === null || isNaN(value)) {
  12284. return NaN;
  12285. }
  12286. return this.getPixelForDecimal(value === this.min
  12287. ? 0
  12288. : (log10(value) - this._startValue) / this._valueRange);
  12289. }
  12290. getValueForPixel(pixel) {
  12291. const decimal = this.getDecimalForPixel(pixel);
  12292. return Math.pow(10, this._startValue + decimal * this._valueRange);
  12293. }
  12294. }
  12295. LogarithmicScale.id = 'logarithmic';
  12296. LogarithmicScale.defaults = {
  12297. ticks: {
  12298. callback: Ticks.formatters.logarithmic,
  12299. major: {
  12300. enabled: true
  12301. }
  12302. }
  12303. };
  12304. function getTickBackdropHeight(opts) {
  12305. const tickOpts = opts.ticks;
  12306. if (tickOpts.display && opts.display) {
  12307. const padding = toPadding(tickOpts.backdropPadding);
  12308. return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
  12309. }
  12310. return 0;
  12311. }
  12312. function measureLabelSize(ctx, font, label) {
  12313. label = isArray(label) ? label : [label];
  12314. return {
  12315. w: _longestText(ctx, font.string, label),
  12316. h: label.length * font.lineHeight
  12317. };
  12318. }
  12319. function determineLimits(angle, pos, size, min, max) {
  12320. if (angle === min || angle === max) {
  12321. return {
  12322. start: pos - (size / 2),
  12323. end: pos + (size / 2)
  12324. };
  12325. } else if (angle < min || angle > max) {
  12326. return {
  12327. start: pos - size,
  12328. end: pos
  12329. };
  12330. }
  12331. return {
  12332. start: pos,
  12333. end: pos + size
  12334. };
  12335. }
  12336. function fitWithPointLabels(scale) {
  12337. const orig = {
  12338. l: scale.left + scale._padding.left,
  12339. r: scale.right - scale._padding.right,
  12340. t: scale.top + scale._padding.top,
  12341. b: scale.bottom - scale._padding.bottom
  12342. };
  12343. const limits = Object.assign({}, orig);
  12344. const labelSizes = [];
  12345. const padding = [];
  12346. const valueCount = scale._pointLabels.length;
  12347. const pointLabelOpts = scale.options.pointLabels;
  12348. const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
  12349. for (let i = 0; i < valueCount; i++) {
  12350. const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
  12351. padding[i] = opts.padding;
  12352. const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
  12353. const plFont = toFont(opts.font);
  12354. const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
  12355. labelSizes[i] = textSize;
  12356. const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
  12357. const angle = Math.round(toDegrees(angleRadians));
  12358. const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  12359. const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  12360. updateLimits(limits, orig, angleRadians, hLimits, vLimits);
  12361. }
  12362. scale.setCenterPoint(
  12363. orig.l - limits.l,
  12364. limits.r - orig.r,
  12365. orig.t - limits.t,
  12366. limits.b - orig.b
  12367. );
  12368. scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  12369. }
  12370. function updateLimits(limits, orig, angle, hLimits, vLimits) {
  12371. const sin = Math.abs(Math.sin(angle));
  12372. const cos = Math.abs(Math.cos(angle));
  12373. let x = 0;
  12374. let y = 0;
  12375. if (hLimits.start < orig.l) {
  12376. x = (orig.l - hLimits.start) / sin;
  12377. limits.l = Math.min(limits.l, orig.l - x);
  12378. } else if (hLimits.end > orig.r) {
  12379. x = (hLimits.end - orig.r) / sin;
  12380. limits.r = Math.max(limits.r, orig.r + x);
  12381. }
  12382. if (vLimits.start < orig.t) {
  12383. y = (orig.t - vLimits.start) / cos;
  12384. limits.t = Math.min(limits.t, orig.t - y);
  12385. } else if (vLimits.end > orig.b) {
  12386. y = (vLimits.end - orig.b) / cos;
  12387. limits.b = Math.max(limits.b, orig.b + y);
  12388. }
  12389. }
  12390. function buildPointLabelItems(scale, labelSizes, padding) {
  12391. const items = [];
  12392. const valueCount = scale._pointLabels.length;
  12393. const opts = scale.options;
  12394. const extra = getTickBackdropHeight(opts) / 2;
  12395. const outerDistance = scale.drawingArea;
  12396. const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0;
  12397. for (let i = 0; i < valueCount; i++) {
  12398. const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle);
  12399. const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
  12400. const size = labelSizes[i];
  12401. const y = yForAngle(pointLabelPosition.y, size.h, angle);
  12402. const textAlign = getTextAlignForAngle(angle);
  12403. const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
  12404. items.push({
  12405. x: pointLabelPosition.x,
  12406. y,
  12407. textAlign,
  12408. left,
  12409. top: y,
  12410. right: left + size.w,
  12411. bottom: y + size.h
  12412. });
  12413. }
  12414. return items;
  12415. }
  12416. function getTextAlignForAngle(angle) {
  12417. if (angle === 0 || angle === 180) {
  12418. return 'center';
  12419. } else if (angle < 180) {
  12420. return 'left';
  12421. }
  12422. return 'right';
  12423. }
  12424. function leftForTextAlign(x, w, align) {
  12425. if (align === 'right') {
  12426. x -= w;
  12427. } else if (align === 'center') {
  12428. x -= (w / 2);
  12429. }
  12430. return x;
  12431. }
  12432. function yForAngle(y, h, angle) {
  12433. if (angle === 90 || angle === 270) {
  12434. y -= (h / 2);
  12435. } else if (angle > 270 || angle < 90) {
  12436. y -= h;
  12437. }
  12438. return y;
  12439. }
  12440. function drawPointLabels(scale, labelCount) {
  12441. const {ctx, options: {pointLabels}} = scale;
  12442. for (let i = labelCount - 1; i >= 0; i--) {
  12443. const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
  12444. const plFont = toFont(optsAtIndex.font);
  12445. const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i];
  12446. const {backdropColor} = optsAtIndex;
  12447. if (!isNullOrUndef(backdropColor)) {
  12448. const padding = toPadding(optsAtIndex.backdropPadding);
  12449. ctx.fillStyle = backdropColor;
  12450. ctx.fillRect(left - padding.left, top - padding.top, right - left + padding.width, bottom - top + padding.height);
  12451. }
  12452. renderText(
  12453. ctx,
  12454. scale._pointLabels[i],
  12455. x,
  12456. y + (plFont.lineHeight / 2),
  12457. plFont,
  12458. {
  12459. color: optsAtIndex.color,
  12460. textAlign: textAlign,
  12461. textBaseline: 'middle'
  12462. }
  12463. );
  12464. }
  12465. }
  12466. function pathRadiusLine(scale, radius, circular, labelCount) {
  12467. const {ctx} = scale;
  12468. if (circular) {
  12469. ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
  12470. } else {
  12471. let pointPosition = scale.getPointPosition(0, radius);
  12472. ctx.moveTo(pointPosition.x, pointPosition.y);
  12473. for (let i = 1; i < labelCount; i++) {
  12474. pointPosition = scale.getPointPosition(i, radius);
  12475. ctx.lineTo(pointPosition.x, pointPosition.y);
  12476. }
  12477. }
  12478. }
  12479. function drawRadiusLine(scale, gridLineOpts, radius, labelCount) {
  12480. const ctx = scale.ctx;
  12481. const circular = gridLineOpts.circular;
  12482. const {color, lineWidth} = gridLineOpts;
  12483. if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) {
  12484. return;
  12485. }
  12486. ctx.save();
  12487. ctx.strokeStyle = color;
  12488. ctx.lineWidth = lineWidth;
  12489. ctx.setLineDash(gridLineOpts.borderDash);
  12490. ctx.lineDashOffset = gridLineOpts.borderDashOffset;
  12491. ctx.beginPath();
  12492. pathRadiusLine(scale, radius, circular, labelCount);
  12493. ctx.closePath();
  12494. ctx.stroke();
  12495. ctx.restore();
  12496. }
  12497. function createPointLabelContext(parent, index, label) {
  12498. return createContext(parent, {
  12499. label,
  12500. index,
  12501. type: 'pointLabel'
  12502. });
  12503. }
  12504. class RadialLinearScale extends LinearScaleBase {
  12505. constructor(cfg) {
  12506. super(cfg);
  12507. this.xCenter = undefined;
  12508. this.yCenter = undefined;
  12509. this.drawingArea = undefined;
  12510. this._pointLabels = [];
  12511. this._pointLabelItems = [];
  12512. }
  12513. setDimensions() {
  12514. const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
  12515. const w = this.width = this.maxWidth - padding.width;
  12516. const h = this.height = this.maxHeight - padding.height;
  12517. this.xCenter = Math.floor(this.left + w / 2 + padding.left);
  12518. this.yCenter = Math.floor(this.top + h / 2 + padding.top);
  12519. this.drawingArea = Math.floor(Math.min(w, h) / 2);
  12520. }
  12521. determineDataLimits() {
  12522. const {min, max} = this.getMinMax(false);
  12523. this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
  12524. this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
  12525. this.handleTickRangeOptions();
  12526. }
  12527. computeTickLimit() {
  12528. return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  12529. }
  12530. generateTickLabels(ticks) {
  12531. LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
  12532. this._pointLabels = this.getLabels()
  12533. .map((value, index) => {
  12534. const label = callback(this.options.pointLabels.callback, [value, index], this);
  12535. return label || label === 0 ? label : '';
  12536. })
  12537. .filter((v, i) => this.chart.getDataVisibility(i));
  12538. }
  12539. fit() {
  12540. const opts = this.options;
  12541. if (opts.display && opts.pointLabels.display) {
  12542. fitWithPointLabels(this);
  12543. } else {
  12544. this.setCenterPoint(0, 0, 0, 0);
  12545. }
  12546. }
  12547. setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
  12548. this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
  12549. this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
  12550. this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
  12551. }
  12552. getIndexAngle(index) {
  12553. const angleMultiplier = TAU / (this._pointLabels.length || 1);
  12554. const startAngle = this.options.startAngle || 0;
  12555. return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
  12556. }
  12557. getDistanceFromCenterForValue(value) {
  12558. if (isNullOrUndef(value)) {
  12559. return NaN;
  12560. }
  12561. const scalingFactor = this.drawingArea / (this.max - this.min);
  12562. if (this.options.reverse) {
  12563. return (this.max - value) * scalingFactor;
  12564. }
  12565. return (value - this.min) * scalingFactor;
  12566. }
  12567. getValueForDistanceFromCenter(distance) {
  12568. if (isNullOrUndef(distance)) {
  12569. return NaN;
  12570. }
  12571. const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
  12572. return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
  12573. }
  12574. getPointLabelContext(index) {
  12575. const pointLabels = this._pointLabels || [];
  12576. if (index >= 0 && index < pointLabels.length) {
  12577. const pointLabel = pointLabels[index];
  12578. return createPointLabelContext(this.getContext(), index, pointLabel);
  12579. }
  12580. }
  12581. getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
  12582. const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
  12583. return {
  12584. x: Math.cos(angle) * distanceFromCenter + this.xCenter,
  12585. y: Math.sin(angle) * distanceFromCenter + this.yCenter,
  12586. angle
  12587. };
  12588. }
  12589. getPointPositionForValue(index, value) {
  12590. return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  12591. }
  12592. getBasePosition(index) {
  12593. return this.getPointPositionForValue(index || 0, this.getBaseValue());
  12594. }
  12595. getPointLabelPosition(index) {
  12596. const {left, top, right, bottom} = this._pointLabelItems[index];
  12597. return {
  12598. left,
  12599. top,
  12600. right,
  12601. bottom,
  12602. };
  12603. }
  12604. drawBackground() {
  12605. const {backgroundColor, grid: {circular}} = this.options;
  12606. if (backgroundColor) {
  12607. const ctx = this.ctx;
  12608. ctx.save();
  12609. ctx.beginPath();
  12610. pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
  12611. ctx.closePath();
  12612. ctx.fillStyle = backgroundColor;
  12613. ctx.fill();
  12614. ctx.restore();
  12615. }
  12616. }
  12617. drawGrid() {
  12618. const ctx = this.ctx;
  12619. const opts = this.options;
  12620. const {angleLines, grid} = opts;
  12621. const labelCount = this._pointLabels.length;
  12622. let i, offset, position;
  12623. if (opts.pointLabels.display) {
  12624. drawPointLabels(this, labelCount);
  12625. }
  12626. if (grid.display) {
  12627. this.ticks.forEach((tick, index) => {
  12628. if (index !== 0) {
  12629. offset = this.getDistanceFromCenterForValue(tick.value);
  12630. const optsAtIndex = grid.setContext(this.getContext(index - 1));
  12631. drawRadiusLine(this, optsAtIndex, offset, labelCount);
  12632. }
  12633. });
  12634. }
  12635. if (angleLines.display) {
  12636. ctx.save();
  12637. for (i = labelCount - 1; i >= 0; i--) {
  12638. const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
  12639. const {color, lineWidth} = optsAtIndex;
  12640. if (!lineWidth || !color) {
  12641. continue;
  12642. }
  12643. ctx.lineWidth = lineWidth;
  12644. ctx.strokeStyle = color;
  12645. ctx.setLineDash(optsAtIndex.borderDash);
  12646. ctx.lineDashOffset = optsAtIndex.borderDashOffset;
  12647. offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
  12648. position = this.getPointPosition(i, offset);
  12649. ctx.beginPath();
  12650. ctx.moveTo(this.xCenter, this.yCenter);
  12651. ctx.lineTo(position.x, position.y);
  12652. ctx.stroke();
  12653. }
  12654. ctx.restore();
  12655. }
  12656. }
  12657. drawBorder() {}
  12658. drawLabels() {
  12659. const ctx = this.ctx;
  12660. const opts = this.options;
  12661. const tickOpts = opts.ticks;
  12662. if (!tickOpts.display) {
  12663. return;
  12664. }
  12665. const startAngle = this.getIndexAngle(0);
  12666. let offset, width;
  12667. ctx.save();
  12668. ctx.translate(this.xCenter, this.yCenter);
  12669. ctx.rotate(startAngle);
  12670. ctx.textAlign = 'center';
  12671. ctx.textBaseline = 'middle';
  12672. this.ticks.forEach((tick, index) => {
  12673. if (index === 0 && !opts.reverse) {
  12674. return;
  12675. }
  12676. const optsAtIndex = tickOpts.setContext(this.getContext(index));
  12677. const tickFont = toFont(optsAtIndex.font);
  12678. offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
  12679. if (optsAtIndex.showLabelBackdrop) {
  12680. ctx.font = tickFont.string;
  12681. width = ctx.measureText(tick.label).width;
  12682. ctx.fillStyle = optsAtIndex.backdropColor;
  12683. const padding = toPadding(optsAtIndex.backdropPadding);
  12684. ctx.fillRect(
  12685. -width / 2 - padding.left,
  12686. -offset - tickFont.size / 2 - padding.top,
  12687. width + padding.width,
  12688. tickFont.size + padding.height
  12689. );
  12690. }
  12691. renderText(ctx, tick.label, 0, -offset, tickFont, {
  12692. color: optsAtIndex.color,
  12693. });
  12694. });
  12695. ctx.restore();
  12696. }
  12697. drawTitle() {}
  12698. }
  12699. RadialLinearScale.id = 'radialLinear';
  12700. RadialLinearScale.defaults = {
  12701. display: true,
  12702. animate: true,
  12703. position: 'chartArea',
  12704. angleLines: {
  12705. display: true,
  12706. lineWidth: 1,
  12707. borderDash: [],
  12708. borderDashOffset: 0.0
  12709. },
  12710. grid: {
  12711. circular: false
  12712. },
  12713. startAngle: 0,
  12714. ticks: {
  12715. showLabelBackdrop: true,
  12716. callback: Ticks.formatters.numeric
  12717. },
  12718. pointLabels: {
  12719. backdropColor: undefined,
  12720. backdropPadding: 2,
  12721. display: true,
  12722. font: {
  12723. size: 10
  12724. },
  12725. callback(label) {
  12726. return label;
  12727. },
  12728. padding: 5,
  12729. centerPointLabels: false
  12730. }
  12731. };
  12732. RadialLinearScale.defaultRoutes = {
  12733. 'angleLines.color': 'borderColor',
  12734. 'pointLabels.color': 'color',
  12735. 'ticks.color': 'color'
  12736. };
  12737. RadialLinearScale.descriptors = {
  12738. angleLines: {
  12739. _fallback: 'grid'
  12740. }
  12741. };
  12742. const INTERVALS = {
  12743. millisecond: {common: true, size: 1, steps: 1000},
  12744. second: {common: true, size: 1000, steps: 60},
  12745. minute: {common: true, size: 60000, steps: 60},
  12746. hour: {common: true, size: 3600000, steps: 24},
  12747. day: {common: true, size: 86400000, steps: 30},
  12748. week: {common: false, size: 604800000, steps: 4},
  12749. month: {common: true, size: 2.628e9, steps: 12},
  12750. quarter: {common: false, size: 7.884e9, steps: 4},
  12751. year: {common: true, size: 3.154e10}
  12752. };
  12753. const UNITS = (Object.keys(INTERVALS));
  12754. function sorter(a, b) {
  12755. return a - b;
  12756. }
  12757. function parse(scale, input) {
  12758. if (isNullOrUndef(input)) {
  12759. return null;
  12760. }
  12761. const adapter = scale._adapter;
  12762. const {parser, round, isoWeekday} = scale._parseOpts;
  12763. let value = input;
  12764. if (typeof parser === 'function') {
  12765. value = parser(value);
  12766. }
  12767. if (!isNumberFinite(value)) {
  12768. value = typeof parser === 'string'
  12769. ? adapter.parse(value, parser)
  12770. : adapter.parse(value);
  12771. }
  12772. if (value === null) {
  12773. return null;
  12774. }
  12775. if (round) {
  12776. value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true)
  12777. ? adapter.startOf(value, 'isoWeek', isoWeekday)
  12778. : adapter.startOf(value, round);
  12779. }
  12780. return +value;
  12781. }
  12782. function determineUnitForAutoTicks(minUnit, min, max, capacity) {
  12783. const ilen = UNITS.length;
  12784. for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
  12785. const interval = INTERVALS[UNITS[i]];
  12786. const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
  12787. if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
  12788. return UNITS[i];
  12789. }
  12790. }
  12791. return UNITS[ilen - 1];
  12792. }
  12793. function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
  12794. for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
  12795. const unit = UNITS[i];
  12796. if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
  12797. return unit;
  12798. }
  12799. }
  12800. return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  12801. }
  12802. function determineMajorUnit(unit) {
  12803. for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
  12804. if (INTERVALS[UNITS[i]].common) {
  12805. return UNITS[i];
  12806. }
  12807. }
  12808. }
  12809. function addTick(ticks, time, timestamps) {
  12810. if (!timestamps) {
  12811. ticks[time] = true;
  12812. } else if (timestamps.length) {
  12813. const {lo, hi} = _lookup(timestamps, time);
  12814. const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
  12815. ticks[timestamp] = true;
  12816. }
  12817. }
  12818. function setMajorTicks(scale, ticks, map, majorUnit) {
  12819. const adapter = scale._adapter;
  12820. const first = +adapter.startOf(ticks[0].value, majorUnit);
  12821. const last = ticks[ticks.length - 1].value;
  12822. let major, index;
  12823. for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
  12824. index = map[major];
  12825. if (index >= 0) {
  12826. ticks[index].major = true;
  12827. }
  12828. }
  12829. return ticks;
  12830. }
  12831. function ticksFromTimestamps(scale, values, majorUnit) {
  12832. const ticks = [];
  12833. const map = {};
  12834. const ilen = values.length;
  12835. let i, value;
  12836. for (i = 0; i < ilen; ++i) {
  12837. value = values[i];
  12838. map[value] = i;
  12839. ticks.push({
  12840. value,
  12841. major: false
  12842. });
  12843. }
  12844. return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  12845. }
  12846. class TimeScale extends Scale {
  12847. constructor(props) {
  12848. super(props);
  12849. this._cache = {
  12850. data: [],
  12851. labels: [],
  12852. all: []
  12853. };
  12854. this._unit = 'day';
  12855. this._majorUnit = undefined;
  12856. this._offsets = {};
  12857. this._normalized = false;
  12858. this._parseOpts = undefined;
  12859. }
  12860. init(scaleOpts, opts) {
  12861. const time = scaleOpts.time || (scaleOpts.time = {});
  12862. const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date);
  12863. mergeIf(time.displayFormats, adapter.formats());
  12864. this._parseOpts = {
  12865. parser: time.parser,
  12866. round: time.round,
  12867. isoWeekday: time.isoWeekday
  12868. };
  12869. super.init(scaleOpts);
  12870. this._normalized = opts.normalized;
  12871. }
  12872. parse(raw, index) {
  12873. if (raw === undefined) {
  12874. return null;
  12875. }
  12876. return parse(this, raw);
  12877. }
  12878. beforeLayout() {
  12879. super.beforeLayout();
  12880. this._cache = {
  12881. data: [],
  12882. labels: [],
  12883. all: []
  12884. };
  12885. }
  12886. determineDataLimits() {
  12887. const options = this.options;
  12888. const adapter = this._adapter;
  12889. const unit = options.time.unit || 'day';
  12890. let {min, max, minDefined, maxDefined} = this.getUserBounds();
  12891. function _applyBounds(bounds) {
  12892. if (!minDefined && !isNaN(bounds.min)) {
  12893. min = Math.min(min, bounds.min);
  12894. }
  12895. if (!maxDefined && !isNaN(bounds.max)) {
  12896. max = Math.max(max, bounds.max);
  12897. }
  12898. }
  12899. if (!minDefined || !maxDefined) {
  12900. _applyBounds(this._getLabelBounds());
  12901. if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
  12902. _applyBounds(this.getMinMax(false));
  12903. }
  12904. }
  12905. min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
  12906. max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
  12907. this.min = Math.min(min, max - 1);
  12908. this.max = Math.max(min + 1, max);
  12909. }
  12910. _getLabelBounds() {
  12911. const arr = this.getLabelTimestamps();
  12912. let min = Number.POSITIVE_INFINITY;
  12913. let max = Number.NEGATIVE_INFINITY;
  12914. if (arr.length) {
  12915. min = arr[0];
  12916. max = arr[arr.length - 1];
  12917. }
  12918. return {min, max};
  12919. }
  12920. buildTicks() {
  12921. const options = this.options;
  12922. const timeOpts = options.time;
  12923. const tickOpts = options.ticks;
  12924. const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
  12925. if (options.bounds === 'ticks' && timestamps.length) {
  12926. this.min = this._userMin || timestamps[0];
  12927. this.max = this._userMax || timestamps[timestamps.length - 1];
  12928. }
  12929. const min = this.min;
  12930. const max = this.max;
  12931. const ticks = _filterBetween(timestamps, min, max);
  12932. this._unit = timeOpts.unit || (tickOpts.autoSkip
  12933. ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min))
  12934. : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
  12935. this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined
  12936. : determineMajorUnit(this._unit);
  12937. this.initOffsets(timestamps);
  12938. if (options.reverse) {
  12939. ticks.reverse();
  12940. }
  12941. return ticksFromTimestamps(this, ticks, this._majorUnit);
  12942. }
  12943. initOffsets(timestamps) {
  12944. let start = 0;
  12945. let end = 0;
  12946. let first, last;
  12947. if (this.options.offset && timestamps.length) {
  12948. first = this.getDecimalForValue(timestamps[0]);
  12949. if (timestamps.length === 1) {
  12950. start = 1 - first;
  12951. } else {
  12952. start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
  12953. }
  12954. last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
  12955. if (timestamps.length === 1) {
  12956. end = last;
  12957. } else {
  12958. end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
  12959. }
  12960. }
  12961. const limit = timestamps.length < 3 ? 0.5 : 0.25;
  12962. start = _limitValue(start, 0, limit);
  12963. end = _limitValue(end, 0, limit);
  12964. this._offsets = {start, end, factor: 1 / (start + 1 + end)};
  12965. }
  12966. _generate() {
  12967. const adapter = this._adapter;
  12968. const min = this.min;
  12969. const max = this.max;
  12970. const options = this.options;
  12971. const timeOpts = options.time;
  12972. const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
  12973. const stepSize = valueOrDefault(timeOpts.stepSize, 1);
  12974. const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
  12975. const hasWeekday = isNumber(weekday) || weekday === true;
  12976. const ticks = {};
  12977. let first = min;
  12978. let time, count;
  12979. if (hasWeekday) {
  12980. first = +adapter.startOf(first, 'isoWeek', weekday);
  12981. }
  12982. first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
  12983. if (adapter.diff(max, min, minor) > 100000 * stepSize) {
  12984. throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
  12985. }
  12986. const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
  12987. for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {
  12988. addTick(ticks, time, timestamps);
  12989. }
  12990. if (time === max || options.bounds === 'ticks' || count === 1) {
  12991. addTick(ticks, time, timestamps);
  12992. }
  12993. return Object.keys(ticks).sort((a, b) => a - b).map(x => +x);
  12994. }
  12995. getLabelForValue(value) {
  12996. const adapter = this._adapter;
  12997. const timeOpts = this.options.time;
  12998. if (timeOpts.tooltipFormat) {
  12999. return adapter.format(value, timeOpts.tooltipFormat);
  13000. }
  13001. return adapter.format(value, timeOpts.displayFormats.datetime);
  13002. }
  13003. _tickFormatFunction(time, index, ticks, format) {
  13004. const options = this.options;
  13005. const formats = options.time.displayFormats;
  13006. const unit = this._unit;
  13007. const majorUnit = this._majorUnit;
  13008. const minorFormat = unit && formats[unit];
  13009. const majorFormat = majorUnit && formats[majorUnit];
  13010. const tick = ticks[index];
  13011. const major = majorUnit && majorFormat && tick && tick.major;
  13012. const label = this._adapter.format(time, format || (major ? majorFormat : minorFormat));
  13013. const formatter = options.ticks.callback;
  13014. return formatter ? callback(formatter, [label, index, ticks], this) : label;
  13015. }
  13016. generateTickLabels(ticks) {
  13017. let i, ilen, tick;
  13018. for (i = 0, ilen = ticks.length; i < ilen; ++i) {
  13019. tick = ticks[i];
  13020. tick.label = this._tickFormatFunction(tick.value, i, ticks);
  13021. }
  13022. }
  13023. getDecimalForValue(value) {
  13024. return value === null ? NaN : (value - this.min) / (this.max - this.min);
  13025. }
  13026. getPixelForValue(value) {
  13027. const offsets = this._offsets;
  13028. const pos = this.getDecimalForValue(value);
  13029. return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
  13030. }
  13031. getValueForPixel(pixel) {
  13032. const offsets = this._offsets;
  13033. const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  13034. return this.min + pos * (this.max - this.min);
  13035. }
  13036. _getLabelSize(label) {
  13037. const ticksOpts = this.options.ticks;
  13038. const tickLabelWidth = this.ctx.measureText(label).width;
  13039. const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
  13040. const cosRotation = Math.cos(angle);
  13041. const sinRotation = Math.sin(angle);
  13042. const tickFontSize = this._resolveTickFontOptions(0).size;
  13043. return {
  13044. w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
  13045. h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
  13046. };
  13047. }
  13048. _getLabelCapacity(exampleTime) {
  13049. const timeOpts = this.options.time;
  13050. const displayFormats = timeOpts.displayFormats;
  13051. const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
  13052. const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format);
  13053. const size = this._getLabelSize(exampleLabel);
  13054. const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
  13055. return capacity > 0 ? capacity : 1;
  13056. }
  13057. getDataTimestamps() {
  13058. let timestamps = this._cache.data || [];
  13059. let i, ilen;
  13060. if (timestamps.length) {
  13061. return timestamps;
  13062. }
  13063. const metas = this.getMatchingVisibleMetas();
  13064. if (this._normalized && metas.length) {
  13065. return (this._cache.data = metas[0].controller.getAllParsedValues(this));
  13066. }
  13067. for (i = 0, ilen = metas.length; i < ilen; ++i) {
  13068. timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
  13069. }
  13070. return (this._cache.data = this.normalize(timestamps));
  13071. }
  13072. getLabelTimestamps() {
  13073. const timestamps = this._cache.labels || [];
  13074. let i, ilen;
  13075. if (timestamps.length) {
  13076. return timestamps;
  13077. }
  13078. const labels = this.getLabels();
  13079. for (i = 0, ilen = labels.length; i < ilen; ++i) {
  13080. timestamps.push(parse(this, labels[i]));
  13081. }
  13082. return (this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps));
  13083. }
  13084. normalize(values) {
  13085. return _arrayUnique(values.sort(sorter));
  13086. }
  13087. }
  13088. TimeScale.id = 'time';
  13089. TimeScale.defaults = {
  13090. bounds: 'data',
  13091. adapters: {},
  13092. time: {
  13093. parser: false,
  13094. unit: false,
  13095. round: false,
  13096. isoWeekday: false,
  13097. minUnit: 'millisecond',
  13098. displayFormats: {}
  13099. },
  13100. ticks: {
  13101. source: 'auto',
  13102. major: {
  13103. enabled: false
  13104. }
  13105. }
  13106. };
  13107. function interpolate(table, val, reverse) {
  13108. let lo = 0;
  13109. let hi = table.length - 1;
  13110. let prevSource, nextSource, prevTarget, nextTarget;
  13111. if (reverse) {
  13112. if (val >= table[lo].pos && val <= table[hi].pos) {
  13113. ({lo, hi} = _lookupByKey(table, 'pos', val));
  13114. }
  13115. ({pos: prevSource, time: prevTarget} = table[lo]);
  13116. ({pos: nextSource, time: nextTarget} = table[hi]);
  13117. } else {
  13118. if (val >= table[lo].time && val <= table[hi].time) {
  13119. ({lo, hi} = _lookupByKey(table, 'time', val));
  13120. }
  13121. ({time: prevSource, pos: prevTarget} = table[lo]);
  13122. ({time: nextSource, pos: nextTarget} = table[hi]);
  13123. }
  13124. const span = nextSource - prevSource;
  13125. return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  13126. }
  13127. class TimeSeriesScale extends TimeScale {
  13128. constructor(props) {
  13129. super(props);
  13130. this._table = [];
  13131. this._minPos = undefined;
  13132. this._tableRange = undefined;
  13133. }
  13134. initOffsets() {
  13135. const timestamps = this._getTimestampsForTable();
  13136. const table = this._table = this.buildLookupTable(timestamps);
  13137. this._minPos = interpolate(table, this.min);
  13138. this._tableRange = interpolate(table, this.max) - this._minPos;
  13139. super.initOffsets(timestamps);
  13140. }
  13141. buildLookupTable(timestamps) {
  13142. const {min, max} = this;
  13143. const items = [];
  13144. const table = [];
  13145. let i, ilen, prev, curr, next;
  13146. for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
  13147. curr = timestamps[i];
  13148. if (curr >= min && curr <= max) {
  13149. items.push(curr);
  13150. }
  13151. }
  13152. if (items.length < 2) {
  13153. return [
  13154. {time: min, pos: 0},
  13155. {time: max, pos: 1}
  13156. ];
  13157. }
  13158. for (i = 0, ilen = items.length; i < ilen; ++i) {
  13159. next = items[i + 1];
  13160. prev = items[i - 1];
  13161. curr = items[i];
  13162. if (Math.round((next + prev) / 2) !== curr) {
  13163. table.push({time: curr, pos: i / (ilen - 1)});
  13164. }
  13165. }
  13166. return table;
  13167. }
  13168. _getTimestampsForTable() {
  13169. let timestamps = this._cache.all || [];
  13170. if (timestamps.length) {
  13171. return timestamps;
  13172. }
  13173. const data = this.getDataTimestamps();
  13174. const label = this.getLabelTimestamps();
  13175. if (data.length && label.length) {
  13176. timestamps = this.normalize(data.concat(label));
  13177. } else {
  13178. timestamps = data.length ? data : label;
  13179. }
  13180. timestamps = this._cache.all = timestamps;
  13181. return timestamps;
  13182. }
  13183. getDecimalForValue(value) {
  13184. return (interpolate(this._table, value) - this._minPos) / this._tableRange;
  13185. }
  13186. getValueForPixel(pixel) {
  13187. const offsets = this._offsets;
  13188. const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
  13189. return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
  13190. }
  13191. }
  13192. TimeSeriesScale.id = 'timeseries';
  13193. TimeSeriesScale.defaults = TimeScale.defaults;
  13194. var scales = /*#__PURE__*/Object.freeze({
  13195. __proto__: null,
  13196. CategoryScale: CategoryScale,
  13197. LinearScale: LinearScale,
  13198. LogarithmicScale: LogarithmicScale,
  13199. RadialLinearScale: RadialLinearScale,
  13200. TimeScale: TimeScale,
  13201. TimeSeriesScale: TimeSeriesScale
  13202. });
  13203. Chart.register(controllers, scales, elements, plugins);
  13204. Chart.helpers = {...helpers};
  13205. Chart._adapters = _adapters;
  13206. Chart.Animation = Animation;
  13207. Chart.Animations = Animations;
  13208. Chart.animator = animator;
  13209. Chart.controllers = registry.controllers.items;
  13210. Chart.DatasetController = DatasetController;
  13211. Chart.Element = Element;
  13212. Chart.elements = elements;
  13213. Chart.Interaction = Interaction;
  13214. Chart.layouts = layouts;
  13215. Chart.platforms = platforms;
  13216. Chart.Scale = Scale;
  13217. Chart.Ticks = Ticks;
  13218. Object.assign(Chart, controllers, scales, elements, plugins, platforms);
  13219. Chart.Chart = Chart;
  13220. if (typeof window !== 'undefined') {
  13221. window.Chart = Chart;
  13222. }
  13223. return Chart;
  13224. }));