plot.umd.js 482 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976
  1. // @observablehq/plot v0.6.17 Copyright 2020-2025 Observable, Inc.
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3@7.9.0/dist/d3.min.js')) :
  4. typeof define === 'function' && define.amd ? define(['exports', 'd3@7.9.0/dist/d3.min.js'], factory) :
  5. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Plot = global.Plot || {}, global.d3));
  6. })(this, (function (exports, d3) { 'use strict';
  7. var version = "0.6.17";
  8. function defined(x) {
  9. return x != null && !Number.isNaN(x);
  10. }
  11. function ascendingDefined(a, b) {
  12. return +defined(b) - +defined(a) || d3.ascending(a, b);
  13. }
  14. function descendingDefined(a, b) {
  15. return +defined(b) - +defined(a) || d3.descending(a, b);
  16. }
  17. function nonempty(x) {
  18. return x != null && `${x}` !== "";
  19. }
  20. function finite$1(x) {
  21. return isFinite(x) ? x : NaN;
  22. }
  23. function positive(x) {
  24. return x > 0 && isFinite(x) ? x : NaN;
  25. }
  26. function negative(x) {
  27. return x < 0 && isFinite(x) ? x : NaN;
  28. }
  29. function format(date, fallback) {
  30. if (!(date instanceof Date)) date = new Date(+date);
  31. if (isNaN(date)) return "string" === "function" ? fallback(date) : fallback;
  32. const hours = date.getUTCHours();
  33. const minutes = date.getUTCMinutes();
  34. const seconds = date.getUTCSeconds();
  35. const milliseconds = date.getUTCMilliseconds();
  36. return `${formatYear(date.getUTCFullYear())}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}${
  37. hours || minutes || seconds || milliseconds ? `T${pad(hours, 2)}:${pad(minutes, 2)}${
  38. seconds || milliseconds ? `:${pad(seconds, 2)}${
  39. milliseconds ? `.${pad(milliseconds, 3)}` : ``
  40. }` : ``
  41. }Z` : ``
  42. }`;
  43. }
  44. function formatYear(year) {
  45. return year < 0 ? `-${pad(-year, 6)}`
  46. : year > 9999 ? `+${pad(year, 6)}`
  47. : pad(year, 4);
  48. }
  49. function pad(value, width) {
  50. return `${value}`.padStart(width, "0");
  51. }
  52. const re = /^(?:[-+]\d{2})?\d{4}(?:-\d{2}(?:-\d{2})?)?(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[-+]\d{2}:?\d{2})?)?$/;
  53. function parse(string, fallback) {
  54. if (!re.test(string += "")) return "undefined" === "function" ? fallback(string) : fallback;
  55. return new Date(string);
  56. }
  57. // Like a sort comparator, returns a positive value if the given array of values
  58. // is in ascending order, a negative value if the values are in descending
  59. // order. Assumes monotonicity; only tests the first and last values.
  60. function orderof(values) {
  61. if (values == null) return;
  62. const first = values[0];
  63. const last = values[values.length - 1];
  64. return d3.descending(first, last);
  65. }
  66. const durationSecond = 1000;
  67. const durationMinute = durationSecond * 60;
  68. const durationHour = durationMinute * 60;
  69. const durationDay = durationHour * 24;
  70. const durationWeek = durationDay * 7;
  71. const durationMonth = durationDay * 30;
  72. const durationYear = durationDay * 365;
  73. // See https://github.com/d3/d3-time/blob/9e8dc940f38f78d7588aad68a54a25b1f0c2d97b/src/ticks.js#L14-L33
  74. const tickIntervals = [
  75. ["millisecond", 1],
  76. ["2 milliseconds", 2],
  77. ["5 milliseconds", 5],
  78. ["10 milliseconds", 10],
  79. ["20 milliseconds", 20],
  80. ["50 milliseconds", 50],
  81. ["100 milliseconds", 100],
  82. ["200 milliseconds", 200],
  83. ["500 milliseconds", 500],
  84. ["second", durationSecond],
  85. ["5 seconds", 5 * durationSecond],
  86. ["15 seconds", 15 * durationSecond],
  87. ["30 seconds", 30 * durationSecond],
  88. ["minute", durationMinute],
  89. ["5 minutes", 5 * durationMinute],
  90. ["15 minutes", 15 * durationMinute],
  91. ["30 minutes", 30 * durationMinute],
  92. ["hour", durationHour],
  93. ["3 hours", 3 * durationHour],
  94. ["6 hours", 6 * durationHour],
  95. ["12 hours", 12 * durationHour],
  96. ["day", durationDay],
  97. ["2 days", 2 * durationDay],
  98. ["week", durationWeek],
  99. ["2 weeks", 2 * durationWeek], // https://github.com/d3/d3-time/issues/46
  100. ["month", durationMonth],
  101. ["3 months", 3 * durationMonth],
  102. ["6 months", 6 * durationMonth], // https://github.com/d3/d3-time/issues/46
  103. ["year", durationYear],
  104. ["2 years", 2 * durationYear],
  105. ["5 years", 5 * durationYear],
  106. ["10 years", 10 * durationYear],
  107. ["20 years", 20 * durationYear],
  108. ["50 years", 50 * durationYear],
  109. ["100 years", 100 * durationYear] // TODO generalize to longer time scales
  110. ];
  111. const durations = new Map([
  112. ["second", durationSecond],
  113. ["minute", durationMinute],
  114. ["hour", durationHour],
  115. ["day", durationDay],
  116. ["monday", durationWeek],
  117. ["tuesday", durationWeek],
  118. ["wednesday", durationWeek],
  119. ["thursday", durationWeek],
  120. ["friday", durationWeek],
  121. ["saturday", durationWeek],
  122. ["sunday", durationWeek],
  123. ["week", durationWeek],
  124. ["month", durationMonth],
  125. ["year", durationYear]
  126. ]);
  127. const timeIntervals = new Map([
  128. ["second", d3.timeSecond],
  129. ["minute", d3.timeMinute],
  130. ["hour", d3.timeHour],
  131. ["day", d3.timeDay], // https://github.com/d3/d3-time/issues/62
  132. ["monday", d3.timeMonday],
  133. ["tuesday", d3.timeTuesday],
  134. ["wednesday", d3.timeWednesday],
  135. ["thursday", d3.timeThursday],
  136. ["friday", d3.timeFriday],
  137. ["saturday", d3.timeSaturday],
  138. ["sunday", d3.timeSunday],
  139. ["week", d3.timeWeek],
  140. ["month", d3.timeMonth],
  141. ["year", d3.timeYear]
  142. ]);
  143. const utcIntervals = new Map([
  144. ["second", d3.utcSecond],
  145. ["minute", d3.utcMinute],
  146. ["hour", d3.utcHour],
  147. ["day", d3.unixDay],
  148. ["monday", d3.utcMonday],
  149. ["tuesday", d3.utcTuesday],
  150. ["wednesday", d3.utcWednesday],
  151. ["thursday", d3.utcThursday],
  152. ["friday", d3.utcFriday],
  153. ["saturday", d3.utcSaturday],
  154. ["sunday", d3.utcSunday],
  155. ["week", d3.utcWeek],
  156. ["month", d3.utcMonth],
  157. ["year", d3.utcYear]
  158. ]);
  159. // These hidden fields describe standard intervals so that we can, for example,
  160. // generalize a scale’s time interval to a larger ticks time interval to reduce
  161. // the number of displayed ticks. TODO We could instead allow the interval
  162. // implementation to expose a “generalize” method that returns a larger, aligned
  163. // interval; that would allow us to move this logic to D3, and allow
  164. // generalization even when a custom interval is provided.
  165. const intervalDuration = Symbol("intervalDuration");
  166. const intervalType = Symbol("intervalType");
  167. // We greedily mutate D3’s standard intervals on load so that the hidden fields
  168. // are available even if specified as e.g. d3.utcMonth instead of "month".
  169. for (const [name, interval] of timeIntervals) {
  170. interval[intervalDuration] = durations.get(name);
  171. interval[intervalType] = "time";
  172. }
  173. for (const [name, interval] of utcIntervals) {
  174. interval[intervalDuration] = durations.get(name);
  175. interval[intervalType] = "utc";
  176. }
  177. const utcFormatIntervals = [
  178. ["year", d3.utcYear, "utc"],
  179. ["month", d3.utcMonth, "utc"],
  180. ["day", d3.unixDay, "utc", 6 * durationMonth],
  181. ["hour", d3.utcHour, "utc", 3 * durationDay],
  182. ["minute", d3.utcMinute, "utc", 6 * durationHour],
  183. ["second", d3.utcSecond, "utc", 30 * durationMinute]
  184. ];
  185. const timeFormatIntervals = [
  186. ["year", d3.timeYear, "time"],
  187. ["month", d3.timeMonth, "time"],
  188. ["day", d3.timeDay, "time", 6 * durationMonth],
  189. ["hour", d3.timeHour, "time", 3 * durationDay],
  190. ["minute", d3.timeMinute, "time", 6 * durationHour],
  191. ["second", d3.timeSecond, "time", 30 * durationMinute]
  192. ];
  193. // An interleaved array of UTC and local time intervals, in descending order
  194. // from largest to smallest, used to determine the most specific standard time
  195. // format for a given array of dates. This is a subset of the tick intervals
  196. // listed above; we only need the breakpoints where the format changes.
  197. const formatIntervals = [
  198. utcFormatIntervals[0],
  199. timeFormatIntervals[0],
  200. utcFormatIntervals[1],
  201. timeFormatIntervals[1],
  202. utcFormatIntervals[2],
  203. timeFormatIntervals[2],
  204. // Below day, local time typically has an hourly offset from UTC and hence the
  205. // two are aligned and indistinguishable; therefore, we only consider UTC, and
  206. // we don’t consider these if the domain only has a single value.
  207. ...utcFormatIntervals.slice(3)
  208. ];
  209. function parseTimeInterval(input) {
  210. let name = `${input}`.toLowerCase();
  211. if (name.endsWith("s")) name = name.slice(0, -1); // drop plural
  212. let period = 1;
  213. const match = /^(?:(\d+)\s+)/.exec(name);
  214. if (match) {
  215. name = name.slice(match[0].length);
  216. period = +match[1];
  217. }
  218. switch (name) {
  219. case "quarter":
  220. name = "month";
  221. period *= 3;
  222. break;
  223. case "half":
  224. name = "month";
  225. period *= 6;
  226. break;
  227. }
  228. let interval = utcIntervals.get(name);
  229. if (!interval) throw new Error(`unknown interval: ${input}`);
  230. if (period > 1 && !interval.every) throw new Error(`non-periodic interval: ${name}`);
  231. return [name, period];
  232. }
  233. function timeInterval(input) {
  234. return asInterval(parseTimeInterval(input), "time");
  235. }
  236. function utcInterval(input) {
  237. return asInterval(parseTimeInterval(input), "utc");
  238. }
  239. function asInterval([name, period], type) {
  240. let interval = (type === "time" ? timeIntervals : utcIntervals).get(name);
  241. if (period > 1) {
  242. interval = interval.every(period);
  243. interval[intervalDuration] = durations.get(name) * period;
  244. interval[intervalType] = type;
  245. }
  246. return interval;
  247. }
  248. // If the given interval is a standard time interval, we may be able to promote
  249. // it a larger aligned time interval, rather than showing every nth tick.
  250. function generalizeTimeInterval(interval, n) {
  251. if (!(n > 1)) return; // no need to generalize
  252. const duration = interval[intervalDuration];
  253. if (!tickIntervals.some(([, d]) => d === duration)) return; // nonstandard or unknown interval
  254. if (duration % durationDay === 0 && durationDay < duration && duration < durationMonth) return; // not generalizable
  255. const [i] = tickIntervals[d3.bisector(([, step]) => Math.log(step)).center(tickIntervals, Math.log(duration * n))];
  256. return (interval[intervalType] === "time" ? timeInterval : utcInterval)(i);
  257. }
  258. function formatTimeInterval(name, type, anchor) {
  259. const format = type === "time" ? d3.timeFormat : d3.utcFormat;
  260. // For tips and legends, use a format that doesn’t require context.
  261. if (anchor == null) {
  262. return format(
  263. name === "year"
  264. ? "%Y"
  265. : name === "month"
  266. ? "%Y-%m"
  267. : name === "day"
  268. ? "%Y-%m-%d"
  269. : name === "hour" || name === "minute"
  270. ? "%Y-%m-%dT%H:%M"
  271. : name === "second"
  272. ? "%Y-%m-%dT%H:%M:%S"
  273. : "%Y-%m-%dT%H:%M:%S.%L"
  274. );
  275. }
  276. // Otherwise, assume that this is for axis ticks.
  277. const template = getTimeTemplate(anchor);
  278. switch (name) {
  279. case "millisecond":
  280. return formatConditional(format(".%L"), format(":%M:%S"), template);
  281. case "second":
  282. return formatConditional(format(":%S"), format("%-I:%M"), template);
  283. case "minute":
  284. return formatConditional(format("%-I:%M"), format("%p"), template);
  285. case "hour":
  286. return formatConditional(format("%-I %p"), format("%b %-d"), template);
  287. case "day":
  288. return formatConditional(format("%-d"), format("%b"), template);
  289. case "month":
  290. return formatConditional(format("%b"), format("%Y"), template);
  291. case "year":
  292. return format("%Y");
  293. }
  294. throw new Error("unable to format time ticks");
  295. }
  296. function getTimeTemplate(anchor) {
  297. return anchor === "left" || anchor === "right"
  298. ? (f1, f2) => `\n${f1}\n${f2}` // extra newline to keep f1 centered
  299. : anchor === "top"
  300. ? (f1, f2) => `${f2}\n${f1}`
  301. : (f1, f2) => `${f1}\n${f2}`;
  302. }
  303. function getFormatIntervals(type) {
  304. return type === "time" ? timeFormatIntervals : type === "utc" ? utcFormatIntervals : formatIntervals;
  305. }
  306. // Given an array of dates, returns the largest compatible standard time
  307. // interval. If no standard interval is compatible (other than milliseconds,
  308. // which is universally compatible), returns undefined.
  309. function inferTimeFormat(type, dates, anchor) {
  310. const step = d3.max(d3.pairs(dates, (a, b) => Math.abs(b - a))); // maybe undefined!
  311. if (step < 1000) return formatTimeInterval("millisecond", "utc", anchor);
  312. for (const [name, interval, intervalType, maxStep] of getFormatIntervals(type)) {
  313. if (step > maxStep) break; // e.g., 52 weeks
  314. if (name === "hour" && !step) break; // e.g., domain with a single date
  315. if (dates.every((d) => interval.floor(d) >= d)) return formatTimeInterval(name, intervalType, anchor);
  316. }
  317. }
  318. function formatConditional(format1, format2, template) {
  319. return (x, i, X) => {
  320. const f1 = format1(x, i); // always shown
  321. const f2 = format2(x, i); // only shown if different
  322. const j = i - orderof(X); // detect reversed domains
  323. return i !== j && X[j] !== undefined && f2 === format2(X[j], j) ? f1 : template(f1, f2);
  324. };
  325. }
  326. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
  327. const TypedArray = Object.getPrototypeOf(Uint8Array);
  328. const objectToString = Object.prototype.toString;
  329. function isArray(value) {
  330. return value instanceof Array || value instanceof TypedArray;
  331. }
  332. function isNumberArray(value) {
  333. return value instanceof TypedArray && !isBigIntArray(value);
  334. }
  335. function isNumberType(type) {
  336. return type?.prototype instanceof TypedArray && !isBigIntType(type);
  337. }
  338. function isBigIntArray(value) {
  339. return value instanceof BigInt64Array || value instanceof BigUint64Array;
  340. }
  341. function isBigIntType(type) {
  342. return type === BigInt64Array || type === BigUint64Array;
  343. }
  344. // If a reindex is attached to the data, channel values expressed as arrays will
  345. // be reindexed when the channels are instantiated. See exclusiveFacets.
  346. const reindex = Symbol("reindex");
  347. function valueof(data, value, type) {
  348. const valueType = typeof value;
  349. return valueType === "string"
  350. ? isArrowTable(data)
  351. ? maybeTypedArrowify(data.getChild(value), type)
  352. : maybeTypedMap(data, field(value), type)
  353. : valueType === "function"
  354. ? maybeTypedMap(data, value, type)
  355. : valueType === "number" || value instanceof Date || valueType === "boolean"
  356. ? map$1(data, constant(value), type)
  357. : typeof value?.transform === "function"
  358. ? maybeTypedArrayify(value.transform(data), type)
  359. : maybeTake(maybeTypedArrayify(value, type), data?.[reindex]);
  360. }
  361. function maybeTake(values, index) {
  362. return values != null && index ? take(values, index) : values;
  363. }
  364. function maybeTypedMap(data, f, type) {
  365. return map$1(data, isNumberType(type) ? (d, i) => coerceNumber(f(d, i)) : f, type); // allow conversion from BigInt
  366. }
  367. function maybeTypedArrayify(data, type) {
  368. return type === undefined
  369. ? arrayify(data) // preserve undefined type
  370. : isArrowVector(data)
  371. ? maybeTypedArrowify(data, type)
  372. : data instanceof type
  373. ? data
  374. : type.from(data, isNumberType(type) && !isNumberArray(data) ? coerceNumber : undefined);
  375. }
  376. function maybeTypedArrowify(vector, type) {
  377. return vector == null
  378. ? vector
  379. : (type === undefined || type === Array) && isArrowDateType(vector.type)
  380. ? coerceDates(vectorToArray(vector))
  381. : maybeTypedArrayify(vectorToArray(vector), type);
  382. }
  383. function vectorToArray(vector) {
  384. return vector.nullCount ? vector.toJSON() : vector.toArray();
  385. }
  386. const singleton = [null]; // for data-less decoration marks, e.g. frame
  387. const field = (name) => (d) => { const v = d[name]; return v === undefined && d.type === "Feature" ? d.properties?.[name] : v; }; // prettier-ignore
  388. const indexOf = {transform: range};
  389. const identity$1 = {transform: (d) => d};
  390. const one = () => 1;
  391. const yes = () => true;
  392. const string = (x) => (x == null ? x : `${x}`);
  393. const number$1 = (x) => (x == null ? x : +x);
  394. const first = (x) => (x ? x[0] : undefined);
  395. const second = (x) => (x ? x[1] : undefined);
  396. const third = (x) => (x ? x[2] : undefined);
  397. const constant = (x) => () => x;
  398. // Converts a string like “p25” into a function that takes an index I and an
  399. // accessor function f, returning the corresponding percentile value.
  400. function percentile(reduce) {
  401. const p = +`${reduce}`.slice(1) / 100;
  402. return (I, f) => d3.quantile(I, p, f);
  403. }
  404. // If the values are specified as a typed array, no coercion is required.
  405. function coerceNumbers(values) {
  406. return isNumberArray(values) ? values : map$1(values, coerceNumber, Float64Array);
  407. }
  408. // Unlike Mark’s number, here we want to convert null and undefined to NaN since
  409. // the result will be stored in a Float64Array and we don’t want null to be
  410. // coerced to zero. We use Number instead of unary + to allow BigInt coercion.
  411. function coerceNumber(x) {
  412. return x == null ? NaN : Number(x);
  413. }
  414. function coerceDates(values) {
  415. return map$1(values, coerceDate);
  416. }
  417. // When coercing strings to dates, we only want to allow the ISO 8601 format
  418. // since the built-in string parsing of the Date constructor varies across
  419. // browsers. (In the future, this could be made more liberal if desired, though
  420. // it is still generally preferable to do date parsing yourself explicitly,
  421. // rather than rely on Plot.) Any non-string values are coerced to number first
  422. // and treated as milliseconds since UNIX epoch.
  423. function coerceDate(x) {
  424. return x instanceof Date && !isNaN(x)
  425. ? x
  426. : typeof x === "string"
  427. ? parse(x)
  428. : x == null || isNaN((x = Number(x))) // allow conversion from BigInt
  429. ? undefined
  430. : new Date(x);
  431. }
  432. // Some channels may allow a string constant to be specified; to differentiate
  433. // string constants (e.g., "red") from named fields (e.g., "date"), this
  434. // function tests whether the given value is a CSS color string and returns a
  435. // tuple [channel, constant] where one of the two is undefined, and the other is
  436. // the given value. If you wish to reference a named field that is also a valid
  437. // CSS color, use an accessor (d => d.red) instead.
  438. function maybeColorChannel(value, defaultValue) {
  439. if (value === undefined) value = defaultValue;
  440. return value === null ? [undefined, "none"] : isColor(value) ? [undefined, value] : [value, undefined];
  441. }
  442. // Similar to maybeColorChannel, this tests whether the given value is a number
  443. // indicating a constant, and otherwise assumes that it’s a channel value.
  444. function maybeNumberChannel(value, defaultValue) {
  445. if (value === undefined) value = defaultValue;
  446. return value === null || typeof value === "number" ? [undefined, value] : [value, undefined];
  447. }
  448. // Validates the specified optional string against the allowed list of keywords.
  449. function maybeKeyword(input, name, allowed) {
  450. if (input != null) return keyword(input, name, allowed);
  451. }
  452. // Validates the specified required string against the allowed list of keywords.
  453. function keyword(input, name, allowed) {
  454. const i = `${input}`.toLowerCase();
  455. if (!allowed.includes(i)) throw new Error(`invalid ${name}: ${input}`);
  456. return i;
  457. }
  458. // Like arrayify, but also allows data to be an Apache Arrow Table.
  459. function dataify(data) {
  460. return isArrowTable(data) ? data : arrayify(data);
  461. }
  462. // Promotes the specified data to an array as needed.
  463. function arrayify(values) {
  464. if (values == null || isArray(values)) return values;
  465. if (isArrowVector(values)) return maybeTypedArrowify(values);
  466. if (isGeoJSON(values)) {
  467. switch (values.type) {
  468. case "FeatureCollection":
  469. return values.features;
  470. case "GeometryCollection":
  471. return values.geometries;
  472. default:
  473. return [values];
  474. }
  475. }
  476. return Array.from(values);
  477. }
  478. // Duck typing test for GeoJSON
  479. function isGeoJSON(x) {
  480. switch (x?.type) {
  481. case "FeatureCollection":
  482. case "GeometryCollection":
  483. case "Feature":
  484. case "LineString":
  485. case "MultiLineString":
  486. case "MultiPoint":
  487. case "MultiPolygon":
  488. case "Point":
  489. case "Polygon":
  490. case "Sphere":
  491. return true;
  492. default:
  493. return false;
  494. }
  495. }
  496. // An optimization of type.from(values, f): if the given values are already an
  497. // instanceof the desired array type, the faster values.map method is used.
  498. function map$1(values, f, type = Array) {
  499. return values == null ? values : values instanceof type ? values.map(f) : type.from(values, f);
  500. }
  501. // An optimization of type.from(values): if the given values are already an
  502. // instanceof the desired array type, the faster values.slice method is used.
  503. function slice(values, type = Array) {
  504. return values instanceof type ? values.slice() : type.from(values);
  505. }
  506. // Returns true if any of x, x1, or x2 is not (strictly) undefined.
  507. function hasX({x, x1, x2}) {
  508. return x !== undefined || x1 !== undefined || x2 !== undefined;
  509. }
  510. // Returns true if any of y, y1, or y2 is not (strictly) undefined.
  511. function hasY({y, y1, y2}) {
  512. return y !== undefined || y1 !== undefined || y2 !== undefined;
  513. }
  514. // Returns true if has x or y, or if interval is not (strictly) undefined.
  515. function hasXY(options) {
  516. return hasX(options) || hasY(options) || options.interval !== undefined;
  517. }
  518. // Disambiguates an options object (e.g., {y: "x2"}) from a primitive value.
  519. function isObject(option) {
  520. return option?.toString === objectToString;
  521. }
  522. // Disambiguates a scale options object (e.g., {color: {type: "linear"}}) from
  523. // some other option (e.g., {color: "red"}). When creating standalone legends,
  524. // this is used to test whether a scale is defined; this should be consistent
  525. // with inferScaleType when there are no channels associated with the scale, and
  526. // if this returns true, then normalizeScale must return non-null.
  527. function isScaleOptions(option) {
  528. return isObject(option) && (option.type !== undefined || option.domain !== undefined);
  529. }
  530. // Disambiguates an options object (e.g., {y: "x2"}) from a channel value
  531. // definition expressed as a channel transform (e.g., {transform: …}).
  532. // TODO Check typeof option[Symbol.iterator] !== "function"?
  533. function isOptions(option) {
  534. return isObject(option) && typeof option.transform !== "function";
  535. }
  536. // Disambiguates a sort transform (e.g., {sort: "date"}) from a channel domain
  537. // sort definition (e.g., {sort: {y: "x"}}).
  538. function isDomainSort(sort) {
  539. return isOptions(sort) && sort.value === undefined && sort.channel === undefined;
  540. }
  541. // For marks specified either as [0, x] or [x1, x2], such as areas and bars.
  542. function maybeZero(x, x1, x2, x3 = identity$1) {
  543. if (x1 === undefined && x2 === undefined) {
  544. // {x} or {}
  545. (x1 = 0), (x2 = x === undefined ? x3 : x);
  546. } else if (x1 === undefined) {
  547. // {x, x2} or {x2}
  548. x1 = x === undefined ? 0 : x;
  549. } else if (x2 === undefined) {
  550. // {x, x1} or {x1}
  551. x2 = x === undefined ? 0 : x;
  552. }
  553. return [x1, x2];
  554. }
  555. // For marks that have x and y channels (e.g., cell, dot, line, text).
  556. function maybeTuple(x, y) {
  557. return x === undefined && y === undefined ? [first, second] : [x, y];
  558. }
  559. // A helper for extracting the z channel, if it is variable. Used by transforms
  560. // that require series, such as moving average and normalize.
  561. function maybeZ({z, fill, stroke} = {}) {
  562. if (z === undefined) [z] = maybeColorChannel(fill);
  563. if (z === undefined) [z] = maybeColorChannel(stroke);
  564. return z;
  565. }
  566. function lengthof(data) {
  567. return isArray(data) ? data.length : data?.numRows;
  568. }
  569. // Returns a Uint32Array with elements [0, 1, 2, … data.length - 1].
  570. function range(data) {
  571. const n = lengthof(data);
  572. const r = new Uint32Array(n);
  573. for (let i = 0; i < n; ++i) r[i] = i;
  574. return r;
  575. }
  576. // Returns an array [values[index[0]], values[index[1]], …].
  577. function take(values, index) {
  578. return isArray(values) ? map$1(index, (i) => values[i], values.constructor) : map$1(index, (i) => values.at(i));
  579. }
  580. // If f does not take exactly one argument, wraps it in a function that uses take.
  581. function taker(f) {
  582. return f.length === 1 ? (index, values) => f(take(values, index)) : f;
  583. }
  584. // Uses subarray if available, and otherwise slice.
  585. function subarray(I, i, j) {
  586. return I.subarray ? I.subarray(i, j) : I.slice(i, j);
  587. }
  588. // Based on InternMap (d3.group).
  589. function keyof(value) {
  590. return value !== null && typeof value === "object" ? value.valueOf() : value;
  591. }
  592. function maybeInput(key, options) {
  593. if (options[key] !== undefined) return options[key];
  594. switch (key) {
  595. case "x1":
  596. case "x2":
  597. key = "x";
  598. break;
  599. case "y1":
  600. case "y2":
  601. key = "y";
  602. break;
  603. }
  604. return options[key];
  605. }
  606. function column(source) {
  607. // Defines a column whose values are lazily populated by calling the returned
  608. // setter. If the given source is labeled, the label is propagated to the
  609. // returned column definition.
  610. let value;
  611. return [
  612. {
  613. transform: () => value,
  614. label: labelof(source)
  615. },
  616. (v) => (value = v)
  617. ];
  618. }
  619. // Like column, but allows the source to be null.
  620. function maybeColumn(source) {
  621. return source == null ? [source] : column(source);
  622. }
  623. function labelof(value, defaultValue) {
  624. return typeof value === "string" ? value : value && value.label !== undefined ? value.label : defaultValue;
  625. }
  626. // Assuming that both x1 and x2 and lazy columns (per above), this derives a new
  627. // a column that’s the average of the two, and which inherits the column label
  628. // (if any). Both input columns are assumed to be quantitative. If either column
  629. // is temporal, the returned column is also temporal.
  630. function mid(x1, x2) {
  631. return {
  632. transform(data) {
  633. const X1 = x1.transform(data);
  634. const X2 = x2.transform(data);
  635. return isTemporal(X1) || isTemporal(X2)
  636. ? map$1(X1, (_, i) => new Date((+X1[i] + +X2[i]) / 2))
  637. : map$1(X1, (_, i) => (+X1[i] + +X2[i]) / 2, Float64Array);
  638. },
  639. label: x1.label
  640. };
  641. }
  642. // If the scale options declare an interval, applies it to the values V.
  643. function maybeApplyInterval(V, scale) {
  644. const t = maybeIntervalTransform(scale?.interval, scale?.type);
  645. return t ? map$1(V, t) : V;
  646. }
  647. // Returns the equivalent scale transform for the specified interval option.
  648. function maybeIntervalTransform(interval, type) {
  649. const i = maybeInterval(interval, type);
  650. return i && ((v) => (defined(v) ? i.floor(v) : v));
  651. }
  652. // If interval is not nullish, converts interval shorthand such as a number (for
  653. // multiples) or a time interval name (such as “day”) to a {floor, offset,
  654. // range} object similar to a D3 time interval.
  655. function maybeInterval(interval, type) {
  656. if (interval == null) return;
  657. if (typeof interval === "number") return numberInterval(interval);
  658. if (typeof interval === "string") return (type === "time" ? timeInterval : utcInterval)(interval);
  659. if (typeof interval.floor !== "function") throw new Error("invalid interval; missing floor method");
  660. if (typeof interval.offset !== "function") throw new Error("invalid interval; missing offset method");
  661. return interval;
  662. }
  663. function numberInterval(interval) {
  664. interval = +interval;
  665. if (0 < interval && interval < 1 && Number.isInteger(1 / interval)) interval = -1 / interval;
  666. const n = Math.abs(interval);
  667. return interval < 0
  668. ? {
  669. floor: (d) => Math.floor(d * n) / n,
  670. offset: (d, s = 1) => (d * n + Math.floor(s)) / n,
  671. range: (lo, hi) => d3.range(Math.ceil(lo * n), hi * n).map((x) => x / n)
  672. }
  673. : {
  674. floor: (d) => Math.floor(d / n) * n,
  675. offset: (d, s = 1) => d + n * Math.floor(s),
  676. range: (lo, hi) => d3.range(Math.ceil(lo / n), hi / n).map((x) => x * n)
  677. };
  678. }
  679. // Like maybeInterval, but requires a range method too.
  680. function maybeRangeInterval(interval, type) {
  681. interval = maybeInterval(interval, type);
  682. if (interval && typeof interval.range !== "function") throw new Error("invalid interval: missing range method");
  683. return interval;
  684. }
  685. // Like maybeRangeInterval, but requires a ceil method too.
  686. function maybeNiceInterval(interval, type) {
  687. interval = maybeRangeInterval(interval, type);
  688. if (interval && typeof interval.ceil !== "function") throw new Error("invalid interval: missing ceil method");
  689. return interval;
  690. }
  691. function isTimeInterval(t) {
  692. return isInterval(t) && typeof t?.floor === "function" && t.floor() instanceof Date;
  693. }
  694. function isInterval(t) {
  695. return typeof t?.range === "function";
  696. }
  697. // This distinguishes between per-dimension options and a standalone value.
  698. function maybeValue(value) {
  699. return value === undefined || isOptions(value) ? value : {value};
  700. }
  701. // Coerces the given channel values (if any) to numbers. This is useful when
  702. // values will be interpolated into other code, such as an SVG transform, and
  703. // where we don’t wish to allow unexpected behavior for weird input.
  704. function numberChannel(source) {
  705. return source == null
  706. ? null
  707. : {
  708. transform: (data) => valueof(data, source, Float64Array),
  709. label: labelof(source)
  710. };
  711. }
  712. function isTuples(data) {
  713. if (!isIterable(data)) return false;
  714. for (const d of data) {
  715. if (d == null) continue;
  716. return typeof d === "object" && "0" in d && "1" in d;
  717. }
  718. }
  719. function isIterable(value) {
  720. return value && typeof value[Symbol.iterator] === "function";
  721. }
  722. function isTextual(values) {
  723. for (const value of values) {
  724. if (value == null) continue;
  725. return typeof value !== "object" || value instanceof Date;
  726. }
  727. }
  728. function isOrdinal(values) {
  729. for (const value of values) {
  730. if (value == null) continue;
  731. const type = typeof value;
  732. return type === "string" || type === "boolean";
  733. }
  734. }
  735. function isTemporal(values) {
  736. for (const value of values) {
  737. if (value == null) continue;
  738. return value instanceof Date;
  739. }
  740. }
  741. // Are these strings that might represent dates? This is stricter than ISO 8601
  742. // because we want to ignore false positives on numbers; for example, the string
  743. // "1192" is more likely to represent a number than a date even though it is
  744. // valid ISO 8601 representing 1192-01-01.
  745. function isTemporalString(values) {
  746. for (const value of values) {
  747. if (value == null) continue;
  748. return typeof value === "string" && isNaN(value) && parse(value);
  749. }
  750. }
  751. // Are these strings that might represent numbers? This is stricter than
  752. // coercion because we want to ignore false positives on e.g. empty strings.
  753. function isNumericString(values) {
  754. for (const value of values) {
  755. if (value == null) continue;
  756. if (typeof value !== "string") return false;
  757. if (!value.trim()) continue;
  758. return !isNaN(value);
  759. }
  760. }
  761. function isNumeric(values) {
  762. for (const value of values) {
  763. if (value == null) continue;
  764. return typeof value === "number";
  765. }
  766. }
  767. // Returns true if every non-null value in the specified iterable of values
  768. // passes the specified predicate, and there is at least one non-null value;
  769. // returns false if at least one non-null value does not pass the specified
  770. // predicate; otherwise returns undefined (as if all values are null).
  771. function isEvery(values, is) {
  772. let every;
  773. for (const value of values) {
  774. if (value == null) continue;
  775. if (!is(value)) return false;
  776. every = true;
  777. }
  778. return every;
  779. }
  780. const namedColors = new Set("none,currentcolor,transparent,aliceblue,antiquewhite,aqua,aquamarine,azure,beige,bisque,black,blanchedalmond,blue,blueviolet,brown,burlywood,cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk,crimson,cyan,darkblue,darkcyan,darkgoldenrod,darkgray,darkgreen,darkgrey,darkkhaki,darkmagenta,darkolivegreen,darkorange,darkorchid,darkred,darksalmon,darkseagreen,darkslateblue,darkslategray,darkslategrey,darkturquoise,darkviolet,deeppink,deepskyblue,dimgray,dimgrey,dodgerblue,firebrick,floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,goldenrod,gray,green,greenyellow,grey,honeydew,hotpink,indianred,indigo,ivory,khaki,lavender,lavenderblush,lawngreen,lemonchiffon,lightblue,lightcoral,lightcyan,lightgoldenrodyellow,lightgray,lightgreen,lightgrey,lightpink,lightsalmon,lightseagreen,lightskyblue,lightslategray,lightslategrey,lightsteelblue,lightyellow,lime,limegreen,linen,magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple,mediumseagreen,mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,midnightblue,mintcream,mistyrose,moccasin,navajowhite,navy,oldlace,olive,olivedrab,orange,orangered,orchid,palegoldenrod,palegreen,paleturquoise,palevioletred,papayawhip,peachpuff,peru,pink,plum,powderblue,purple,rebeccapurple,red,rosybrown,royalblue,saddlebrown,salmon,sandybrown,seagreen,seashell,sienna,silver,skyblue,slateblue,slategray,slategrey,snow,springgreen,steelblue,tan,teal,thistle,tomato,turquoise,violet,wheat,white,whitesmoke,yellow".split(",")); // prettier-ignore
  781. // Returns true if value is a valid CSS color string. This is intentionally lax
  782. // because the CSS color spec keeps growing, and we don’t need to parse these
  783. // colors—we just need to disambiguate them from column names.
  784. // https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
  785. // https://www.w3.org/TR/css-color-5/
  786. function isColor(value) {
  787. if (typeof value !== "string") return false;
  788. value = value.toLowerCase().trim();
  789. return (
  790. /^#[0-9a-f]{3,8}$/.test(value) || // hex rgb, rgba, rrggbb, rrggbbaa
  791. /^(?:url|var|rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix)\(.*\)$/.test(value) || // <funciri>, CSS variable, color, etc.
  792. namedColors.has(value) // currentColor, red, etc.
  793. );
  794. }
  795. function isOpacity(value) {
  796. return typeof value === "number" && ((0 <= value && value <= 1) || isNaN(value));
  797. }
  798. function isNoneish(value) {
  799. return value == null || isNone(value);
  800. }
  801. function isNone(value) {
  802. return /^\s*none\s*$/i.test(value);
  803. }
  804. function isRound(value) {
  805. return /^\s*round\s*$/i.test(value);
  806. }
  807. function maybeAnchor$3(value, name) {
  808. return maybeKeyword(value, name, [
  809. "middle",
  810. "top-left",
  811. "top",
  812. "top-right",
  813. "right",
  814. "bottom-right",
  815. "bottom",
  816. "bottom-left",
  817. "left"
  818. ]);
  819. }
  820. function maybeFrameAnchor(value = "middle") {
  821. return maybeAnchor$3(value, "frameAnchor");
  822. }
  823. // Unlike {...defaults, ...options}, this ensures that any undefined (but
  824. // present) properties in options inherit the given default value.
  825. function inherit(options = {}, ...rest) {
  826. let o = options;
  827. for (const defaults of rest) {
  828. for (const key in defaults) {
  829. if (o[key] === undefined) {
  830. const value = defaults[key];
  831. if (o === options) o = {...o, [key]: value};
  832. else o[key] = value;
  833. }
  834. }
  835. }
  836. return o;
  837. }
  838. // Given an iterable of named things (objects with a name property), returns a
  839. // corresponding object with properties associated with the given name.
  840. function named(things) {
  841. console.warn("named iterables are deprecated; please use an object instead");
  842. const names = new Set();
  843. return Object.fromEntries(
  844. Array.from(things, (thing) => {
  845. const {name} = thing;
  846. if (name == null) throw new Error("missing name");
  847. const key = `${name}`;
  848. if (key === "__proto__") throw new Error(`illegal name: ${key}`);
  849. if (names.has(key)) throw new Error(`duplicate name: ${key}`);
  850. names.add(key);
  851. return [name, thing];
  852. })
  853. );
  854. }
  855. function maybeNamed(things) {
  856. return isIterable(things) ? named(things) : things;
  857. }
  858. function maybeClip(clip) {
  859. if (clip === true) clip = "frame";
  860. else if (clip === false) clip = null;
  861. else if (!isGeoJSON(clip) && clip != null) {
  862. clip = keyword(clip, "clip", ["frame", "sphere"]);
  863. if (clip === "sphere") clip = {type: "Sphere"};
  864. }
  865. return clip;
  866. }
  867. // https://github.com/observablehq/stdlib/blob/746ca2e69135df6178e4f3a17244def35d8d6b20/src/arrow.js#L4C1-L17C1
  868. function isArrowTable(value) {
  869. return (
  870. value &&
  871. typeof value.getChild === "function" &&
  872. typeof value.toArray === "function" &&
  873. value.schema &&
  874. Array.isArray(value.schema.fields)
  875. );
  876. }
  877. function isArrowVector(value) {
  878. return value && typeof value.toArray === "function" && value.type;
  879. }
  880. // Apache Arrow now represents dates as numbers. We currently only support
  881. // implicit coercion to JavaScript Date objects when the numbers represent
  882. // milliseconds since Unix epoch.
  883. function isArrowDateType(type) {
  884. return (
  885. type &&
  886. (type.typeId === 8 || // date
  887. type.typeId === 10) && // timestamp
  888. type.unit === 1 // millisecond
  889. );
  890. }
  891. // Positional scales have associated axes, and for ordinal data, a point or band
  892. // scale is used instead of an ordinal scale.
  893. const position$1 = Symbol("position");
  894. // Color scales default to the turbo interpolator for quantitative data, and to
  895. // the Tableau10 scheme for ordinal data. Color scales may also have an
  896. // associated legend.
  897. const color = Symbol("color");
  898. // Radius scales default to the sqrt type, have a default range of [0, 3], and a
  899. // default domain from 0 to the median first quartile of associated channels.
  900. const radius = Symbol("radius");
  901. // Length scales default to the linear type, have a default range of [0, 12],
  902. // and a default domain from 0 to the median median of associated channels.
  903. const length = Symbol("length");
  904. // Opacity scales have a default range of [0, 1], and a default domain from 0 to
  905. // the maximum value of associated channels.
  906. const opacity = Symbol("opacity");
  907. // Symbol scales have a default range of categorical symbols.
  908. const symbol = Symbol("symbol");
  909. // There isn’t really a projection scale; this represents x and y for geometry.
  910. // This is used to denote channels that should be projected.
  911. const projection = Symbol("projection");
  912. // TODO Rather than hard-coding the list of known scale names, collect the names
  913. // and categories for each plot specification, so that custom marks can register
  914. // custom scales.
  915. const registry = new Map([
  916. ["x", position$1],
  917. ["y", position$1],
  918. ["fx", position$1],
  919. ["fy", position$1],
  920. ["r", radius],
  921. ["color", color],
  922. ["opacity", opacity],
  923. ["symbol", symbol],
  924. ["length", length],
  925. ["projection", projection]
  926. ]);
  927. function isPosition(kind) {
  928. return kind === position$1 || kind === projection;
  929. }
  930. function hasNumericRange(kind) {
  931. return kind === position$1 || kind === radius || kind === length || kind === opacity;
  932. }
  933. const sqrt3 = Math.sqrt(3);
  934. const sqrt4_3 = 2 / sqrt3;
  935. const symbolHexagon = {
  936. draw(context, size) {
  937. const rx = Math.sqrt(size / Math.PI),
  938. ry = rx * sqrt4_3,
  939. hy = ry / 2;
  940. context.moveTo(0, ry);
  941. context.lineTo(rx, hy);
  942. context.lineTo(rx, -hy);
  943. context.lineTo(0, -ry);
  944. context.lineTo(-rx, -hy);
  945. context.lineTo(-rx, hy);
  946. context.closePath();
  947. }
  948. };
  949. const symbols = new Map([
  950. ["asterisk", d3.symbolAsterisk],
  951. ["circle", d3.symbolCircle],
  952. ["cross", d3.symbolCross],
  953. ["diamond", d3.symbolDiamond],
  954. ["diamond2", d3.symbolDiamond2],
  955. ["hexagon", symbolHexagon],
  956. ["plus", d3.symbolPlus],
  957. ["square", d3.symbolSquare],
  958. ["square2", d3.symbolSquare2],
  959. ["star", d3.symbolStar],
  960. ["times", d3.symbolTimes],
  961. ["triangle", d3.symbolTriangle],
  962. ["triangle2", d3.symbolTriangle2],
  963. ["wye", d3.symbolWye]
  964. ]);
  965. function isSymbolObject(value) {
  966. return value && typeof value.draw === "function";
  967. }
  968. function isSymbol(value) {
  969. if (isSymbolObject(value)) return true;
  970. if (typeof value !== "string") return false;
  971. return symbols.has(value.toLowerCase());
  972. }
  973. function maybeSymbol(symbol) {
  974. if (symbol == null || isSymbolObject(symbol)) return symbol;
  975. const value = symbols.get(`${symbol}`.toLowerCase());
  976. if (value) return value;
  977. throw new Error(`invalid symbol: ${symbol}`);
  978. }
  979. function maybeSymbolChannel(symbol) {
  980. if (symbol == null || isSymbolObject(symbol)) return [undefined, symbol];
  981. if (typeof symbol === "string") {
  982. const value = symbols.get(`${symbol}`.toLowerCase());
  983. if (value) return [undefined, value];
  984. }
  985. return [symbol, undefined];
  986. }
  987. function basic({filter: f1, sort: s1, reverse: r1, transform: t1, initializer: i1, ...options} = {}, transform) {
  988. // If both t1 and t2 are defined, returns a composite transform that first
  989. // applies t1 and then applies t2.
  990. if (t1 === undefined) {
  991. // explicit transform overrides filter, sort, and reverse
  992. if (f1 != null) t1 = filterTransform(f1);
  993. if (s1 != null && !isDomainSort(s1)) t1 = composeTransform(t1, sortTransform(s1));
  994. if (r1) t1 = composeTransform(t1, reverseTransform);
  995. }
  996. if (transform != null && i1 != null) throw new Error("transforms cannot be applied after initializers");
  997. return {
  998. ...options,
  999. ...((s1 === null || isDomainSort(s1)) && {sort: s1}),
  1000. transform: composeTransform(t1, transform)
  1001. };
  1002. }
  1003. function initializer({filter: f1, sort: s1, reverse: r1, initializer: i1, ...options} = {}, initializer) {
  1004. // If both i1 and i2 are defined, returns a composite initializer that first
  1005. // applies i1 and then applies i2.
  1006. if (i1 === undefined) {
  1007. // explicit initializer overrides filter, sort, and reverse
  1008. if (f1 != null) i1 = filterTransform(f1);
  1009. if (s1 != null && !isDomainSort(s1)) i1 = composeInitializer(i1, sortTransform(s1));
  1010. if (r1) i1 = composeInitializer(i1, reverseTransform);
  1011. }
  1012. return {
  1013. ...options,
  1014. ...((s1 === null || isDomainSort(s1)) && {sort: s1}),
  1015. initializer: composeInitializer(i1, initializer)
  1016. };
  1017. }
  1018. function composeTransform(t1, t2) {
  1019. if (t1 == null) return t2 === null ? undefined : t2;
  1020. if (t2 == null) return t1 === null ? undefined : t1;
  1021. return function (data, facets, plotOptions) {
  1022. ({data, facets} = t1.call(this, data, facets, plotOptions));
  1023. return t2.call(this, dataify(data), facets, plotOptions);
  1024. };
  1025. }
  1026. function composeInitializer(i1, i2) {
  1027. if (i1 == null) return i2 === null ? undefined : i2;
  1028. if (i2 == null) return i1 === null ? undefined : i1;
  1029. return function (data, facets, channels, ...args) {
  1030. let c1, d1, f1, c2, d2, f2;
  1031. ({data: d1 = data, facets: f1 = facets, channels: c1} = i1.call(this, data, facets, channels, ...args));
  1032. ({data: d2 = d1, facets: f2 = f1, channels: c2} = i2.call(this, d1, f1, {...channels, ...c1}, ...args));
  1033. return {data: d2, facets: f2, channels: {...c1, ...c2}};
  1034. };
  1035. }
  1036. function apply(options, t) {
  1037. return (options.initializer != null ? initializer : basic)(options, t);
  1038. }
  1039. function filter(test, options) {
  1040. return apply(options, filterTransform(test));
  1041. }
  1042. function filterTransform(value) {
  1043. return (data, facets) => {
  1044. const V = valueof(data, value);
  1045. return {data, facets: facets.map((I) => I.filter((i) => V[i]))};
  1046. };
  1047. }
  1048. function reverse({sort, ...options} = {}) {
  1049. return {
  1050. ...apply(options, reverseTransform),
  1051. sort: isDomainSort(sort) ? sort : null
  1052. };
  1053. }
  1054. function reverseTransform(data, facets) {
  1055. return {data, facets: facets.map((I) => I.slice().reverse())};
  1056. }
  1057. function shuffle({seed, sort, ...options} = {}) {
  1058. return {
  1059. ...apply(options, sortValue(seed == null ? Math.random : d3.randomLcg(seed))),
  1060. sort: isDomainSort(sort) ? sort : null
  1061. };
  1062. }
  1063. function sort(order, {sort, ...options} = {}) {
  1064. return {
  1065. ...(isOptions(order) && order.channel !== undefined ? initializer : apply)(options, sortTransform(order)),
  1066. sort: isDomainSort(sort) ? sort : null
  1067. };
  1068. }
  1069. function sortTransform(value) {
  1070. return (typeof value === "function" && value.length !== 1 ? sortData : sortValue)(value);
  1071. }
  1072. function sortData(compare) {
  1073. return (data, facets) => {
  1074. const compareData = isArray(data)
  1075. ? (i, j) => compare(data[i], data[j])
  1076. : (i, j) => compare(data.get(i), data.get(j));
  1077. return {data, facets: facets.map((I) => I.slice().sort(compareData))};
  1078. };
  1079. }
  1080. function sortValue(value) {
  1081. let channel, order;
  1082. ({channel, value, order} = {...maybeValue(value)});
  1083. const negate = channel?.startsWith("-");
  1084. if (negate) channel = channel.slice(1);
  1085. if (order === undefined) order = negate ? descendingDefined : ascendingDefined;
  1086. if (typeof order !== "function") {
  1087. switch (`${order}`.toLowerCase()) {
  1088. case "ascending":
  1089. order = ascendingDefined;
  1090. break;
  1091. case "descending":
  1092. order = descendingDefined;
  1093. break;
  1094. default:
  1095. throw new Error(`invalid order: ${order}`);
  1096. }
  1097. }
  1098. return (data, facets, channels) => {
  1099. let V;
  1100. if (channel === undefined) {
  1101. V = valueof(data, value);
  1102. } else {
  1103. if (channels === undefined) throw new Error("channel sort requires an initializer");
  1104. V = channels[channel];
  1105. if (!V) return {}; // ignore missing channel
  1106. V = V.value;
  1107. }
  1108. const compareValue = (i, j) => order(V[i], V[j]);
  1109. return {data, facets: facets.map((I) => I.slice().sort(compareValue))};
  1110. };
  1111. }
  1112. // Group on {z, fill, stroke}.
  1113. function groupZ$1(outputs, options) {
  1114. return groupn(null, null, outputs, options);
  1115. }
  1116. // Group on {z, fill, stroke}, then on x.
  1117. function groupX(outputs = {y: "count"}, options = {}) {
  1118. const {x = identity$1} = options;
  1119. if (x == null) throw new Error("missing channel: x");
  1120. return groupn(x, null, outputs, options);
  1121. }
  1122. // Group on {z, fill, stroke}, then on y.
  1123. function groupY(outputs = {x: "count"}, options = {}) {
  1124. const {y = identity$1} = options;
  1125. if (y == null) throw new Error("missing channel: y");
  1126. return groupn(null, y, outputs, options);
  1127. }
  1128. // Group on {z, fill, stroke}, then on x and y.
  1129. function group(outputs = {fill: "count"}, options = {}) {
  1130. let {x, y} = options;
  1131. [x, y] = maybeTuple(x, y);
  1132. if (x == null) throw new Error("missing channel: x");
  1133. if (y == null) throw new Error("missing channel: y");
  1134. return groupn(x, y, outputs, options);
  1135. }
  1136. function groupn(
  1137. x, // optionally group on x
  1138. y, // optionally group on y
  1139. {
  1140. data: reduceData = reduceIdentity,
  1141. filter,
  1142. sort,
  1143. reverse,
  1144. ...outputs // output channel definitions
  1145. } = {},
  1146. inputs = {} // input channels and options
  1147. ) {
  1148. // Compute the outputs.
  1149. outputs = maybeGroupOutputs(outputs, inputs);
  1150. reduceData = maybeGroupReduce(reduceData, identity$1);
  1151. sort = sort == null ? undefined : maybeGroupOutput("sort", sort, inputs);
  1152. filter = filter == null ? undefined : maybeGroupEvaluator("filter", filter, inputs);
  1153. // Produce x and y output channels as appropriate.
  1154. const [GX, setGX] = maybeColumn(x);
  1155. const [GY, setGY] = maybeColumn(y);
  1156. // Greedily materialize the z, fill, and stroke channels (if channels and not
  1157. // constants) so that we can reference them for subdividing groups without
  1158. // computing them more than once.
  1159. const {
  1160. z,
  1161. fill,
  1162. stroke,
  1163. x1,
  1164. x2, // consumed if x is an output
  1165. y1,
  1166. y2, // consumed if y is an output
  1167. ...options
  1168. } = inputs;
  1169. const [GZ, setGZ] = maybeColumn(z);
  1170. const [vfill] = maybeColorChannel(fill);
  1171. const [vstroke] = maybeColorChannel(stroke);
  1172. const [GF, setGF] = maybeColumn(vfill);
  1173. const [GS, setGS] = maybeColumn(vstroke);
  1174. return {
  1175. ...("z" in inputs && {z: GZ || z}),
  1176. ...("fill" in inputs && {fill: GF || fill}),
  1177. ...("stroke" in inputs && {stroke: GS || stroke}),
  1178. ...basic(options, (data, facets, plotOptions) => {
  1179. const X = maybeApplyInterval(valueof(data, x), plotOptions?.x);
  1180. const Y = maybeApplyInterval(valueof(data, y), plotOptions?.y);
  1181. const Z = valueof(data, z);
  1182. const F = valueof(data, vfill);
  1183. const S = valueof(data, vstroke);
  1184. const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S});
  1185. const groupFacets = [];
  1186. const groupData = [];
  1187. const GX = X && setGX([]);
  1188. const GY = Y && setGY([]);
  1189. const GZ = Z && setGZ([]);
  1190. const GF = F && setGF([]);
  1191. const GS = S && setGS([]);
  1192. let i = 0;
  1193. for (const o of outputs) o.initialize(data);
  1194. if (sort) sort.initialize(data);
  1195. if (filter) filter.initialize(data);
  1196. for (const facet of facets) {
  1197. const groupFacet = [];
  1198. for (const o of outputs) o.scope("facet", facet);
  1199. if (sort) sort.scope("facet", facet);
  1200. if (filter) filter.scope("facet", facet);
  1201. for (const [f, I] of maybeGroup(facet, G)) {
  1202. for (const [y, gg] of maybeGroup(I, Y)) {
  1203. for (const [x, g] of maybeGroup(gg, X)) {
  1204. const extent = {data};
  1205. if (X) extent.x = x;
  1206. if (Y) extent.y = y;
  1207. if (G) extent.z = f;
  1208. if (filter && !filter.reduce(g, extent)) continue;
  1209. groupFacet.push(i++);
  1210. groupData.push(reduceData.reduceIndex(g, data, extent));
  1211. if (X) GX.push(x);
  1212. if (Y) GY.push(y);
  1213. if (Z) GZ.push(G === Z ? f : Z[g[0]]);
  1214. if (F) GF.push(G === F ? f : F[g[0]]);
  1215. if (S) GS.push(G === S ? f : S[g[0]]);
  1216. for (const o of outputs) o.reduce(g, extent);
  1217. if (sort) sort.reduce(g, extent);
  1218. }
  1219. }
  1220. }
  1221. groupFacets.push(groupFacet);
  1222. }
  1223. maybeSort(groupFacets, sort, reverse);
  1224. return {data: groupData, facets: groupFacets};
  1225. }),
  1226. ...(!hasOutput(outputs, "x") && (GX ? {x: GX} : {x1, x2})),
  1227. ...(!hasOutput(outputs, "y") && (GY ? {y: GY} : {y1, y2})),
  1228. ...Object.fromEntries(outputs.map(({name, output}) => [name, output]))
  1229. };
  1230. }
  1231. function hasOutput(outputs, ...names) {
  1232. for (const {name} of outputs) {
  1233. if (names.includes(name)) {
  1234. return true;
  1235. }
  1236. }
  1237. return false;
  1238. }
  1239. function maybeOutputs(outputs, inputs, asOutput = maybeOutput) {
  1240. const entries = Object.entries(outputs);
  1241. // Propagate standard mark channels by default.
  1242. if (inputs.title != null && outputs.title === undefined) entries.push(["title", reduceTitle]);
  1243. if (inputs.href != null && outputs.href === undefined) entries.push(["href", reduceFirst$1]);
  1244. return entries
  1245. .filter(([, reduce]) => reduce !== undefined)
  1246. .map(([name, reduce]) => (reduce === null ? nullOutput(name) : asOutput(name, reduce, inputs)));
  1247. }
  1248. function maybeOutput(name, reduce, inputs, asEvaluator = maybeEvaluator) {
  1249. let scale; // optional per-channel scale override
  1250. if (isObject(reduce) && "reduce" in reduce) (scale = reduce.scale), (reduce = reduce.reduce); // N.B. array.reduce
  1251. const evaluator = asEvaluator(name, reduce, inputs);
  1252. const [output, setOutput] = column(evaluator.label);
  1253. let O;
  1254. return {
  1255. name,
  1256. output: scale === undefined ? output : {value: output, scale},
  1257. initialize(data) {
  1258. evaluator.initialize(data);
  1259. O = setOutput([]);
  1260. },
  1261. scope(scope, I) {
  1262. evaluator.scope(scope, I);
  1263. },
  1264. reduce(I, extent) {
  1265. O.push(evaluator.reduce(I, extent));
  1266. }
  1267. };
  1268. }
  1269. function nullOutput(name) {
  1270. return {name, initialize() {}, scope() {}, reduce() {}};
  1271. }
  1272. function maybeEvaluator(name, reduce, inputs, asReduce = maybeReduce$1) {
  1273. const input = maybeInput(name, inputs);
  1274. const reducer = asReduce(reduce, input);
  1275. let V, context;
  1276. return {
  1277. label: labelof(reducer === reduceCount ? null : input, reducer.label),
  1278. initialize(data) {
  1279. V = input === undefined ? data : valueof(data, input);
  1280. if (reducer.scope === "data") {
  1281. context = reducer.reduceIndex(range(data), V);
  1282. }
  1283. },
  1284. scope(scope, I) {
  1285. if (reducer.scope === scope) {
  1286. context = reducer.reduceIndex(I, V);
  1287. }
  1288. },
  1289. reduce(I, extent) {
  1290. return reducer.scope == null ? reducer.reduceIndex(I, V, extent) : reducer.reduceIndex(I, V, context, extent);
  1291. }
  1292. };
  1293. }
  1294. function maybeGroup(I, X) {
  1295. return X ? d3.group(I, (i) => X[i]) : [[, I]];
  1296. }
  1297. function maybeReduce$1(reduce, value, fallback = invalidReduce) {
  1298. if (reduce == null) return fallback(reduce);
  1299. if (typeof reduce.reduceIndex === "function") return reduce;
  1300. if (typeof reduce.reduce === "function" && isObject(reduce)) return reduceReduce(reduce); // N.B. array.reduce
  1301. if (typeof reduce === "function") return reduceFunction(reduce);
  1302. if (/^p\d{2}$/i.test(reduce)) return reduceAccessor$1(percentile(reduce));
  1303. switch (`${reduce}`.toLowerCase()) {
  1304. case "first":
  1305. return reduceFirst$1;
  1306. case "last":
  1307. return reduceLast$1;
  1308. case "identity":
  1309. return reduceIdentity;
  1310. case "count":
  1311. return reduceCount;
  1312. case "distinct":
  1313. return reduceDistinct;
  1314. case "sum":
  1315. return value == null ? reduceCount : reduceSum$1;
  1316. case "proportion":
  1317. return reduceProportion(value, "data");
  1318. case "proportion-facet":
  1319. return reduceProportion(value, "facet");
  1320. case "deviation":
  1321. return reduceAccessor$1(d3.deviation);
  1322. case "min":
  1323. return reduceAccessor$1(d3.min);
  1324. case "min-index":
  1325. return reduceAccessor$1(d3.minIndex);
  1326. case "max":
  1327. return reduceAccessor$1(d3.max);
  1328. case "max-index":
  1329. return reduceAccessor$1(d3.maxIndex);
  1330. case "mean":
  1331. return reduceMaybeTemporalAccessor(d3.mean);
  1332. case "median":
  1333. return reduceMaybeTemporalAccessor(d3.median);
  1334. case "variance":
  1335. return reduceAccessor$1(d3.variance);
  1336. case "mode":
  1337. return reduceAccessor$1(d3.mode);
  1338. }
  1339. return fallback(reduce);
  1340. }
  1341. function invalidReduce(reduce) {
  1342. throw new Error(`invalid reduce: ${reduce}`);
  1343. }
  1344. function maybeGroupOutputs(outputs, inputs) {
  1345. return maybeOutputs(outputs, inputs, maybeGroupOutput);
  1346. }
  1347. function maybeGroupOutput(name, reduce, inputs) {
  1348. return maybeOutput(name, reduce, inputs, maybeGroupEvaluator);
  1349. }
  1350. function maybeGroupEvaluator(name, reduce, inputs) {
  1351. return maybeEvaluator(name, reduce, inputs, maybeGroupReduce);
  1352. }
  1353. function maybeGroupReduce(reduce, value) {
  1354. return maybeReduce$1(reduce, value, maybeGroupReduceFallback);
  1355. }
  1356. function maybeGroupReduceFallback(reduce) {
  1357. switch (`${reduce}`.toLowerCase()) {
  1358. case "x":
  1359. return reduceX$1;
  1360. case "y":
  1361. return reduceY$1;
  1362. case "z":
  1363. return reduceZ;
  1364. }
  1365. throw new Error(`invalid group reduce: ${reduce}`);
  1366. }
  1367. function maybeSubgroup(outputs, inputs) {
  1368. for (const name in inputs) {
  1369. const value = inputs[name];
  1370. if (value !== undefined && !outputs.some((o) => o.name === name)) {
  1371. return value;
  1372. }
  1373. }
  1374. }
  1375. function maybeSort(facets, sort, reverse) {
  1376. if (sort) {
  1377. const S = sort.output.transform();
  1378. const compare = (i, j) => ascendingDefined(S[i], S[j]);
  1379. facets.forEach((f) => f.sort(compare));
  1380. }
  1381. if (reverse) {
  1382. facets.forEach((f) => f.reverse());
  1383. }
  1384. }
  1385. function reduceReduce(reduce) {
  1386. console.warn("deprecated reduce interface; implement reduceIndex instead.");
  1387. return {...reduce, reduceIndex: reduce.reduce.bind(reduce)};
  1388. }
  1389. function reduceFunction(f) {
  1390. return {
  1391. reduceIndex(I, X, extent) {
  1392. return f(take(X, I), extent);
  1393. }
  1394. };
  1395. }
  1396. function reduceAccessor$1(f) {
  1397. return {
  1398. reduceIndex(I, X) {
  1399. return f(I, (i) => X[i]);
  1400. }
  1401. };
  1402. }
  1403. function reduceMaybeTemporalAccessor(f) {
  1404. return {
  1405. reduceIndex(I, X) {
  1406. const x = f(I, (i) => X[i]);
  1407. return isTemporal(X) ? new Date(x) : x;
  1408. }
  1409. };
  1410. }
  1411. const reduceIdentity = {
  1412. reduceIndex(I, X) {
  1413. return take(X, I);
  1414. }
  1415. };
  1416. const reduceFirst$1 = {
  1417. reduceIndex(I, X) {
  1418. return X[I[0]];
  1419. }
  1420. };
  1421. const reduceTitle = {
  1422. reduceIndex(I, X) {
  1423. const n = 5;
  1424. const groups = d3.sort(
  1425. d3.rollup(
  1426. I,
  1427. (V) => V.length,
  1428. (i) => X[i]
  1429. ),
  1430. second
  1431. );
  1432. const top = groups.slice(-5).reverse();
  1433. if (top.length < groups.length) {
  1434. const bottom = groups.slice(0, 1 - n);
  1435. top[n - 1] = [`… ${bottom.length.toLocaleString("en-US")} more`, d3.sum(bottom, second)];
  1436. }
  1437. return top.map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`).join("\n");
  1438. }
  1439. };
  1440. const reduceLast$1 = {
  1441. reduceIndex(I, X) {
  1442. return X[I[I.length - 1]];
  1443. }
  1444. };
  1445. const reduceCount = {
  1446. label: "Frequency",
  1447. reduceIndex(I) {
  1448. return I.length;
  1449. }
  1450. };
  1451. const reduceDistinct = {
  1452. label: "Distinct",
  1453. reduceIndex(I, X) {
  1454. const s = new d3.InternSet();
  1455. for (const i of I) s.add(X[i]);
  1456. return s.size;
  1457. }
  1458. };
  1459. const reduceSum$1 = reduceAccessor$1(d3.sum);
  1460. function reduceProportion(value, scope) {
  1461. return value == null
  1462. ? {scope, label: "Frequency", reduceIndex: (I, V, basis = 1) => I.length / basis}
  1463. : {scope, reduceIndex: (I, V, basis = 1) => d3.sum(I, (i) => V[i]) / basis};
  1464. }
  1465. const reduceX$1 = {
  1466. reduceIndex(I, X, {x}) {
  1467. return x;
  1468. }
  1469. };
  1470. const reduceY$1 = {
  1471. reduceIndex(I, X, {y}) {
  1472. return y;
  1473. }
  1474. };
  1475. const reduceZ = {
  1476. reduceIndex(I, X, {z}) {
  1477. return z;
  1478. }
  1479. };
  1480. function find(test) {
  1481. if (typeof test !== "function") throw new Error(`invalid test function: ${test}`);
  1482. return {
  1483. reduceIndex(I, V, {data}) {
  1484. return V[I.find(isArray(data) ? (i) => test(data[i], i, data) : (i) => test(data.get(i), i, data))];
  1485. }
  1486. };
  1487. }
  1488. function createChannel(data, {scale, type, value, filter, hint, label = labelof(value)}, name) {
  1489. if (hint === undefined && typeof value?.transform === "function") hint = value.hint;
  1490. return inferChannelScale(name, {
  1491. scale,
  1492. type,
  1493. value: valueof(data, value),
  1494. label,
  1495. filter,
  1496. hint
  1497. });
  1498. }
  1499. function createChannels(channels, data) {
  1500. return Object.fromEntries(
  1501. Object.entries(channels).map(([name, channel]) => [name, createChannel(data, channel, name)])
  1502. );
  1503. }
  1504. // TODO Use Float64Array for scales with numeric ranges, e.g. position?
  1505. function valueObject(channels, scales) {
  1506. const values = Object.fromEntries(
  1507. Object.entries(channels).map(([name, {scale: scaleName, value}]) => {
  1508. const scale = scaleName == null ? null : scales[scaleName];
  1509. return [name, scale == null ? value : map$1(value, scale)];
  1510. })
  1511. );
  1512. values.channels = channels; // expose channel state for advanced usage
  1513. return values;
  1514. }
  1515. // If the channel uses the "auto" scale (or equivalently true), infer the scale
  1516. // from the channel name and the provided values. For color and symbol channels,
  1517. // no scale is applied if the values are literal; however for symbols, we must
  1518. // promote symbol names (e.g., "plus") to symbol implementations (symbolPlus).
  1519. // Note: mutates channel!
  1520. function inferChannelScale(name, channel) {
  1521. const {scale, value} = channel;
  1522. if (scale === true || scale === "auto") {
  1523. switch (name) {
  1524. case "fill":
  1525. case "stroke":
  1526. case "color":
  1527. channel.scale = scale !== true && isEvery(value, isColor) ? null : "color";
  1528. channel.defaultScale = "color";
  1529. break;
  1530. case "fillOpacity":
  1531. case "strokeOpacity":
  1532. case "opacity":
  1533. channel.scale = scale !== true && isEvery(value, isOpacity) ? null : "opacity";
  1534. channel.defaultScale = "opacity";
  1535. break;
  1536. case "symbol":
  1537. if (scale !== true && isEvery(value, isSymbol)) {
  1538. channel.scale = null;
  1539. channel.value = map$1(value, maybeSymbol);
  1540. } else {
  1541. channel.scale = "symbol";
  1542. }
  1543. channel.defaultScale = "symbol";
  1544. break;
  1545. default:
  1546. channel.scale = registry.has(name) ? name : null;
  1547. break;
  1548. }
  1549. } else if (scale === false) {
  1550. channel.scale = null;
  1551. } else if (scale != null && !registry.has(scale)) {
  1552. throw new Error(`unknown scale: ${scale}`);
  1553. }
  1554. return channel;
  1555. }
  1556. // Note: mutates channel.domain! This is set to a function so that it is lazily
  1557. // computed; i.e., if the scale’s domain is set explicitly, that takes priority
  1558. // over the sort option, and we don’t need to do additional work.
  1559. function channelDomain(data, facets, channels, facetChannels, options) {
  1560. const {order: defaultOrder, reverse: defaultReverse, reduce: defaultReduce = true, limit: defaultLimit} = options;
  1561. for (const x in options) {
  1562. if (!registry.has(x)) continue; // ignore unknown scale keys (including generic options)
  1563. let {value: y, order = defaultOrder, reverse = defaultReverse, reduce = defaultReduce, limit = defaultLimit} = maybeValue(options[x]); // prettier-ignore
  1564. const negate = y?.startsWith("-");
  1565. if (negate) y = y.slice(1);
  1566. order = order === undefined ? negate !== (y === "width" || y === "height") ? descendingGroup : ascendingGroup : maybeOrder$1(order); // prettier-ignore
  1567. if (reduce == null || reduce === false) continue; // disabled reducer
  1568. const X = x === "fx" || x === "fy" ? reindexFacetChannel(facets, facetChannels[x]) : findScaleChannel(channels, x);
  1569. if (!X) throw new Error(`missing channel for scale: ${x}`);
  1570. const XV = X.value;
  1571. const [lo = 0, hi = Infinity] = isIterable(limit) ? limit : limit < 0 ? [limit] : [0, limit];
  1572. if (y == null) {
  1573. X.domain = () => {
  1574. let domain = Array.from(new d3.InternSet(XV)); // remove any duplicates
  1575. if (reverse) domain = domain.reverse();
  1576. if (lo !== 0 || hi !== Infinity) domain = domain.slice(lo, hi);
  1577. return domain;
  1578. };
  1579. } else {
  1580. const YV =
  1581. y === "data"
  1582. ? data
  1583. : y === "height"
  1584. ? difference(channels, "y1", "y2")
  1585. : y === "width"
  1586. ? difference(channels, "x1", "x2")
  1587. : values(channels, y, y === "y" ? "y2" : y === "x" ? "x2" : undefined);
  1588. const reducer = maybeReduce$1(reduce === true ? "max" : reduce, YV);
  1589. X.domain = () => {
  1590. let domain = d3.rollups(
  1591. range(XV),
  1592. (I) => reducer.reduceIndex(I, YV),
  1593. (i) => XV[i]
  1594. );
  1595. if (order) domain.sort(order);
  1596. if (reverse) domain.reverse();
  1597. if (lo !== 0 || hi !== Infinity) domain = domain.slice(lo, hi);
  1598. return domain.map(first);
  1599. };
  1600. }
  1601. }
  1602. }
  1603. function findScaleChannel(channels, scale) {
  1604. for (const name in channels) {
  1605. const channel = channels[name];
  1606. if (channel.scale === scale) return channel;
  1607. }
  1608. }
  1609. // Facet channels are not affected by transforms; so, to compute the domain of a
  1610. // facet scale, we must first re-index the facet channel according to the
  1611. // transformed mark index. Note: mutates channel, but that should be safe here?
  1612. function reindexFacetChannel(facets, channel) {
  1613. const originalFacets = facets.original;
  1614. if (originalFacets === facets) return channel; // not transformed
  1615. const V1 = channel.value;
  1616. const V2 = (channel.value = []); // mutates channel!
  1617. for (let i = 0; i < originalFacets.length; ++i) {
  1618. const vi = V1[originalFacets[i][0]];
  1619. for (const j of facets[i]) V2[j] = vi;
  1620. }
  1621. return channel;
  1622. }
  1623. function difference(channels, k1, k2) {
  1624. const X1 = values(channels, k1);
  1625. const X2 = values(channels, k2);
  1626. return map$1(X2, (x2, i) => Math.abs(x2 - X1[i]), Float64Array);
  1627. }
  1628. function values(channels, name, alias) {
  1629. let channel = channels[name];
  1630. if (!channel && alias !== undefined) channel = channels[alias];
  1631. if (channel) return channel.value;
  1632. throw new Error(`missing channel: ${name}`);
  1633. }
  1634. function maybeOrder$1(order) {
  1635. if (order == null || typeof order === "function") return order;
  1636. switch (`${order}`.toLowerCase()) {
  1637. case "ascending":
  1638. return ascendingGroup;
  1639. case "descending":
  1640. return descendingGroup;
  1641. }
  1642. throw new Error(`invalid order: ${order}`);
  1643. }
  1644. function ascendingGroup([ak, av], [bk, bv]) {
  1645. return ascendingDefined(av, bv) || ascendingDefined(ak, bk);
  1646. }
  1647. function descendingGroup([ak, av], [bk, bv]) {
  1648. return descendingDefined(av, bv) || ascendingDefined(ak, bk);
  1649. }
  1650. function getSource(channels, key) {
  1651. let channel = channels[key];
  1652. if (!channel) return;
  1653. while (channel.source) channel = channel.source;
  1654. return channel.source === null ? null : channel;
  1655. }
  1656. const categoricalSchemes = new Map([
  1657. ["accent", d3.schemeAccent],
  1658. ["category10", d3.schemeCategory10],
  1659. ["dark2", d3.schemeDark2],
  1660. ["observable10", d3.schemeObservable10],
  1661. ["paired", d3.schemePaired],
  1662. ["pastel1", d3.schemePastel1],
  1663. ["pastel2", d3.schemePastel2],
  1664. ["set1", d3.schemeSet1],
  1665. ["set2", d3.schemeSet2],
  1666. ["set3", d3.schemeSet3],
  1667. ["tableau10", d3.schemeTableau10]
  1668. ]);
  1669. function isCategoricalScheme(scheme) {
  1670. return scheme != null && categoricalSchemes.has(`${scheme}`.toLowerCase());
  1671. }
  1672. const ordinalSchemes = new Map([
  1673. ...categoricalSchemes,
  1674. // diverging
  1675. ["brbg", scheme11(d3.schemeBrBG, d3.interpolateBrBG)],
  1676. ["prgn", scheme11(d3.schemePRGn, d3.interpolatePRGn)],
  1677. ["piyg", scheme11(d3.schemePiYG, d3.interpolatePiYG)],
  1678. ["puor", scheme11(d3.schemePuOr, d3.interpolatePuOr)],
  1679. ["rdbu", scheme11(d3.schemeRdBu, d3.interpolateRdBu)],
  1680. ["rdgy", scheme11(d3.schemeRdGy, d3.interpolateRdGy)],
  1681. ["rdylbu", scheme11(d3.schemeRdYlBu, d3.interpolateRdYlBu)],
  1682. ["rdylgn", scheme11(d3.schemeRdYlGn, d3.interpolateRdYlGn)],
  1683. ["spectral", scheme11(d3.schemeSpectral, d3.interpolateSpectral)],
  1684. // reversed diverging (for temperature data)
  1685. ["burd", scheme11r(d3.schemeRdBu, d3.interpolateRdBu)],
  1686. ["buylrd", scheme11r(d3.schemeRdYlBu, d3.interpolateRdYlBu)],
  1687. // sequential (single-hue)
  1688. ["blues", scheme9(d3.schemeBlues, d3.interpolateBlues)],
  1689. ["greens", scheme9(d3.schemeGreens, d3.interpolateGreens)],
  1690. ["greys", scheme9(d3.schemeGreys, d3.interpolateGreys)],
  1691. ["oranges", scheme9(d3.schemeOranges, d3.interpolateOranges)],
  1692. ["purples", scheme9(d3.schemePurples, d3.interpolatePurples)],
  1693. ["reds", scheme9(d3.schemeReds, d3.interpolateReds)],
  1694. // sequential (multi-hue)
  1695. ["turbo", schemei(d3.interpolateTurbo)],
  1696. ["viridis", schemei(d3.interpolateViridis)],
  1697. ["magma", schemei(d3.interpolateMagma)],
  1698. ["inferno", schemei(d3.interpolateInferno)],
  1699. ["plasma", schemei(d3.interpolatePlasma)],
  1700. ["cividis", schemei(d3.interpolateCividis)],
  1701. ["cubehelix", schemei(d3.interpolateCubehelixDefault)],
  1702. ["warm", schemei(d3.interpolateWarm)],
  1703. ["cool", schemei(d3.interpolateCool)],
  1704. ["bugn", scheme9(d3.schemeBuGn, d3.interpolateBuGn)],
  1705. ["bupu", scheme9(d3.schemeBuPu, d3.interpolateBuPu)],
  1706. ["gnbu", scheme9(d3.schemeGnBu, d3.interpolateGnBu)],
  1707. ["orrd", scheme9(d3.schemeOrRd, d3.interpolateOrRd)],
  1708. ["pubu", scheme9(d3.schemePuBu, d3.interpolatePuBu)],
  1709. ["pubugn", scheme9(d3.schemePuBuGn, d3.interpolatePuBuGn)],
  1710. ["purd", scheme9(d3.schemePuRd, d3.interpolatePuRd)],
  1711. ["rdpu", scheme9(d3.schemeRdPu, d3.interpolateRdPu)],
  1712. ["ylgn", scheme9(d3.schemeYlGn, d3.interpolateYlGn)],
  1713. ["ylgnbu", scheme9(d3.schemeYlGnBu, d3.interpolateYlGnBu)],
  1714. ["ylorbr", scheme9(d3.schemeYlOrBr, d3.interpolateYlOrBr)],
  1715. ["ylorrd", scheme9(d3.schemeYlOrRd, d3.interpolateYlOrRd)],
  1716. // cyclical
  1717. ["rainbow", schemeicyclical(d3.interpolateRainbow)],
  1718. ["sinebow", schemeicyclical(d3.interpolateSinebow)]
  1719. ]);
  1720. function scheme9(scheme, interpolate) {
  1721. return ({length: n}) => {
  1722. if (n === 1) return [scheme[3][1]]; // favor midpoint
  1723. if (n === 2) return [scheme[3][1], scheme[3][2]]; // favor darker
  1724. n = Math.max(3, Math.floor(n));
  1725. return n > 9 ? d3.quantize(interpolate, n) : scheme[n];
  1726. };
  1727. }
  1728. function scheme11(scheme, interpolate) {
  1729. return ({length: n}) => {
  1730. if (n === 2) return [scheme[3][0], scheme[3][2]]; // favor diverging extrema
  1731. n = Math.max(3, Math.floor(n));
  1732. return n > 11 ? d3.quantize(interpolate, n) : scheme[n];
  1733. };
  1734. }
  1735. function scheme11r(scheme, interpolate) {
  1736. return ({length: n}) => {
  1737. if (n === 2) return [scheme[3][2], scheme[3][0]]; // favor diverging extrema
  1738. n = Math.max(3, Math.floor(n));
  1739. return n > 11 ? d3.quantize((t) => interpolate(1 - t), n) : scheme[n].slice().reverse();
  1740. };
  1741. }
  1742. function schemei(interpolate) {
  1743. return ({length: n}) => d3.quantize(interpolate, Math.max(2, Math.floor(n)));
  1744. }
  1745. function schemeicyclical(interpolate) {
  1746. return ({length: n}) => d3.quantize(interpolate, Math.floor(n) + 1).slice(0, -1);
  1747. }
  1748. function ordinalScheme(scheme) {
  1749. const s = `${scheme}`.toLowerCase();
  1750. if (!ordinalSchemes.has(s)) throw new Error(`unknown ordinal scheme: ${s}`);
  1751. return ordinalSchemes.get(s);
  1752. }
  1753. function ordinalRange(scheme, length) {
  1754. const s = ordinalScheme(scheme);
  1755. const r = typeof s === "function" ? s({length}) : s;
  1756. return r.length !== length ? r.slice(0, length) : r;
  1757. }
  1758. // If the specified domain contains only booleans (ignoring null and undefined),
  1759. // returns a corresponding range where false is mapped to the low color and true
  1760. // is mapped to the high color of the specified scheme.
  1761. function maybeBooleanRange(domain, scheme = "greys") {
  1762. const range = new Set();
  1763. const [f, t] = ordinalRange(scheme, 2);
  1764. for (const value of domain) {
  1765. if (value == null) continue;
  1766. if (value === true) range.add(t);
  1767. else if (value === false) range.add(f);
  1768. else return;
  1769. }
  1770. return [...range];
  1771. }
  1772. const quantitativeSchemes = new Map([
  1773. // diverging
  1774. ["brbg", d3.interpolateBrBG],
  1775. ["prgn", d3.interpolatePRGn],
  1776. ["piyg", d3.interpolatePiYG],
  1777. ["puor", d3.interpolatePuOr],
  1778. ["rdbu", d3.interpolateRdBu],
  1779. ["rdgy", d3.interpolateRdGy],
  1780. ["rdylbu", d3.interpolateRdYlBu],
  1781. ["rdylgn", d3.interpolateRdYlGn],
  1782. ["spectral", d3.interpolateSpectral],
  1783. // reversed diverging (for temperature data)
  1784. ["burd", (t) => d3.interpolateRdBu(1 - t)],
  1785. ["buylrd", (t) => d3.interpolateRdYlBu(1 - t)],
  1786. // sequential (single-hue)
  1787. ["blues", d3.interpolateBlues],
  1788. ["greens", d3.interpolateGreens],
  1789. ["greys", d3.interpolateGreys],
  1790. ["purples", d3.interpolatePurples],
  1791. ["reds", d3.interpolateReds],
  1792. ["oranges", d3.interpolateOranges],
  1793. // sequential (multi-hue)
  1794. ["turbo", d3.interpolateTurbo],
  1795. ["viridis", d3.interpolateViridis],
  1796. ["magma", d3.interpolateMagma],
  1797. ["inferno", d3.interpolateInferno],
  1798. ["plasma", d3.interpolatePlasma],
  1799. ["cividis", d3.interpolateCividis],
  1800. ["cubehelix", d3.interpolateCubehelixDefault],
  1801. ["warm", d3.interpolateWarm],
  1802. ["cool", d3.interpolateCool],
  1803. ["bugn", d3.interpolateBuGn],
  1804. ["bupu", d3.interpolateBuPu],
  1805. ["gnbu", d3.interpolateGnBu],
  1806. ["orrd", d3.interpolateOrRd],
  1807. ["pubugn", d3.interpolatePuBuGn],
  1808. ["pubu", d3.interpolatePuBu],
  1809. ["purd", d3.interpolatePuRd],
  1810. ["rdpu", d3.interpolateRdPu],
  1811. ["ylgnbu", d3.interpolateYlGnBu],
  1812. ["ylgn", d3.interpolateYlGn],
  1813. ["ylorbr", d3.interpolateYlOrBr],
  1814. ["ylorrd", d3.interpolateYlOrRd],
  1815. // cyclical
  1816. ["rainbow", d3.interpolateRainbow],
  1817. ["sinebow", d3.interpolateSinebow]
  1818. ]);
  1819. function quantitativeScheme(scheme) {
  1820. const s = `${scheme}`.toLowerCase();
  1821. if (!quantitativeSchemes.has(s)) throw new Error(`unknown quantitative scheme: ${s}`);
  1822. return quantitativeSchemes.get(s);
  1823. }
  1824. const divergingSchemes = new Set([
  1825. "brbg",
  1826. "prgn",
  1827. "piyg",
  1828. "puor",
  1829. "rdbu",
  1830. "rdgy",
  1831. "rdylbu",
  1832. "rdylgn",
  1833. "spectral",
  1834. "burd",
  1835. "buylrd"
  1836. ]);
  1837. function isDivergingScheme(scheme) {
  1838. return scheme != null && divergingSchemes.has(`${scheme}`.toLowerCase());
  1839. }
  1840. const flip = (i) => (t) => i(1 - t);
  1841. const unit = [0, 1];
  1842. const interpolators = new Map([
  1843. // numbers
  1844. ["number", d3.interpolateNumber],
  1845. // color spaces
  1846. ["rgb", d3.interpolateRgb],
  1847. ["hsl", d3.interpolateHsl],
  1848. ["hcl", d3.interpolateHcl],
  1849. ["lab", d3.interpolateLab]
  1850. ]);
  1851. function maybeInterpolator(interpolate) {
  1852. const i = `${interpolate}`.toLowerCase();
  1853. if (!interpolators.has(i)) throw new Error(`unknown interpolator: ${i}`);
  1854. return interpolators.get(i);
  1855. }
  1856. function createScaleQ(
  1857. key,
  1858. scale,
  1859. channels,
  1860. {
  1861. type,
  1862. nice,
  1863. clamp,
  1864. zero,
  1865. domain = inferAutoDomain(key, channels),
  1866. unknown,
  1867. round,
  1868. scheme,
  1869. interval,
  1870. range = registry.get(key) === radius
  1871. ? inferRadialRange(channels, domain)
  1872. : registry.get(key) === length
  1873. ? inferLengthRange(channels, domain)
  1874. : registry.get(key) === opacity
  1875. ? unit
  1876. : undefined,
  1877. interpolate = registry.get(key) === color
  1878. ? scheme == null && range !== undefined
  1879. ? d3.interpolateRgb
  1880. : quantitativeScheme(scheme !== undefined ? scheme : type === "cyclical" ? "rainbow" : "turbo")
  1881. : round
  1882. ? d3.interpolateRound
  1883. : d3.interpolateNumber,
  1884. reverse
  1885. }
  1886. ) {
  1887. domain = maybeRepeat(domain);
  1888. interval = maybeRangeInterval(interval, type);
  1889. if (type === "cyclical" || type === "sequential") type = "linear"; // shorthand for color schemes
  1890. if (typeof interpolate !== "function") interpolate = maybeInterpolator(interpolate); // named interpolator
  1891. reverse = !!reverse;
  1892. // If an explicit range is specified, and it has a different length than the
  1893. // domain, then redistribute the range using a piecewise interpolator.
  1894. if (range !== undefined) {
  1895. const n = domain.length;
  1896. const m = (range = maybeRepeat(range)).length;
  1897. if (n !== m) {
  1898. if (interpolate.length === 1) throw new Error("invalid piecewise interpolator"); // e.g., turbo
  1899. interpolate = d3.piecewise(interpolate, range);
  1900. range = undefined;
  1901. }
  1902. }
  1903. // Disambiguate between a two-argument interpolator that is used in
  1904. // conjunction with the range, and a one-argument “fixed” interpolator on the
  1905. // [0, 1] interval as with the RdBu color scheme.
  1906. if (interpolate.length === 1) {
  1907. if (reverse) {
  1908. interpolate = flip(interpolate);
  1909. reverse = false;
  1910. }
  1911. if (range === undefined) {
  1912. range = Float64Array.from(domain, (_, i) => i / (domain.length - 1));
  1913. if (range.length === 2) range = unit; // optimize common case of [0, 1]
  1914. }
  1915. scale.interpolate((range === unit ? constant : interpolatePiecewise)(interpolate));
  1916. } else {
  1917. scale.interpolate(interpolate);
  1918. }
  1919. // If a zero option is specified, we assume that the domain is numeric, and we
  1920. // want to ensure that the domain crosses zero. However, note that the domain
  1921. // may be reversed (descending) so we shouldn’t assume that the first value is
  1922. // smaller than the last; and also it’s possible that the domain has more than
  1923. // two values for a “poly” scale. And lastly be careful not to mutate input!
  1924. if (zero) {
  1925. const [min, max] = d3.extent(domain);
  1926. if (min > 0 || max < 0) {
  1927. domain = slice(domain);
  1928. const o = orderof(domain) || 1; // treat degenerate as ascending
  1929. if (o === Math.sign(min)) domain[0] = 0; // [1, 2] or [-1, -2]
  1930. else domain[domain.length - 1] = 0; // [2, 1] or [-2, -1]
  1931. }
  1932. }
  1933. if (reverse) domain = d3.reverse(domain);
  1934. scale.domain(domain).unknown(unknown);
  1935. if (nice) scale.nice(maybeNice(nice, type)), (domain = scale.domain());
  1936. if (range !== undefined) scale.range(range);
  1937. if (clamp) scale.clamp(clamp);
  1938. return {type, domain, range, scale, interpolate, interval};
  1939. }
  1940. function maybeRepeat(values) {
  1941. values = arrayify(values);
  1942. return values.length >= 2 ? values : [values[0], values[0]];
  1943. }
  1944. function maybeNice(nice, type) {
  1945. return nice === true ? undefined : typeof nice === "number" ? nice : maybeNiceInterval(nice, type);
  1946. }
  1947. function createScaleLinear(key, channels, options) {
  1948. return createScaleQ(key, d3.scaleLinear(), channels, options);
  1949. }
  1950. function createScaleSqrt(key, channels, options) {
  1951. return createScalePow(key, channels, {...options, exponent: 0.5});
  1952. }
  1953. function createScalePow(key, channels, {exponent = 1, ...options}) {
  1954. return createScaleQ(key, d3.scalePow().exponent(exponent), channels, {...options, type: "pow"});
  1955. }
  1956. function createScaleLog(key, channels, {base = 10, domain = inferLogDomain(channels), ...options}) {
  1957. return createScaleQ(key, d3.scaleLog().base(base), channels, {...options, domain});
  1958. }
  1959. function createScaleSymlog(key, channels, {constant = 1, ...options}) {
  1960. return createScaleQ(key, d3.scaleSymlog().constant(constant), channels, options);
  1961. }
  1962. function createScaleQuantile(
  1963. key,
  1964. channels,
  1965. {
  1966. range,
  1967. quantiles = range === undefined ? 5 : (range = [...range]).length, // deprecated; use n instead
  1968. n = quantiles,
  1969. scheme = "rdylbu",
  1970. domain = inferQuantileDomain(channels),
  1971. unknown,
  1972. interpolate,
  1973. reverse
  1974. }
  1975. ) {
  1976. if (range === undefined) {
  1977. range =
  1978. interpolate !== undefined
  1979. ? d3.quantize(interpolate, n)
  1980. : registry.get(key) === color
  1981. ? ordinalRange(scheme, n)
  1982. : undefined;
  1983. }
  1984. if (domain.length > 0) {
  1985. domain = d3.scaleQuantile(domain, range === undefined ? {length: n} : range).quantiles();
  1986. }
  1987. return createScaleThreshold(key, channels, {domain, range, reverse, unknown});
  1988. }
  1989. function createScaleQuantize(
  1990. key,
  1991. channels,
  1992. {
  1993. range,
  1994. n = range === undefined ? 5 : (range = [...range]).length,
  1995. scheme = "rdylbu",
  1996. domain = inferAutoDomain(key, channels),
  1997. unknown,
  1998. interpolate,
  1999. reverse
  2000. }
  2001. ) {
  2002. const [min, max] = d3.extent(domain);
  2003. let thresholds;
  2004. if (range === undefined) {
  2005. thresholds = d3.ticks(min, max, n); // approximate number of nice, round thresholds
  2006. if (thresholds[0] <= min) thresholds.splice(0, 1); // drop exact lower bound
  2007. if (thresholds[thresholds.length - 1] >= max) thresholds.pop(); // drop exact upper bound
  2008. n = thresholds.length + 1;
  2009. range =
  2010. interpolate !== undefined
  2011. ? d3.quantize(interpolate, n)
  2012. : registry.get(key) === color
  2013. ? ordinalRange(scheme, n)
  2014. : undefined;
  2015. } else {
  2016. thresholds = d3.quantize(d3.interpolateNumber(min, max), n + 1).slice(1, -1); // exactly n - 1 thresholds to match range
  2017. if (min instanceof Date) thresholds = thresholds.map((x) => new Date(x)); // preserve date types
  2018. }
  2019. if (orderof(arrayify(domain)) < 0) thresholds.reverse(); // preserve descending domain
  2020. return createScaleThreshold(key, channels, {domain: thresholds, range, reverse, unknown});
  2021. }
  2022. function createScaleThreshold(
  2023. key,
  2024. channels,
  2025. {
  2026. domain = [0], // explicit thresholds in ascending order
  2027. unknown,
  2028. scheme = "rdylbu",
  2029. interpolate,
  2030. range = interpolate !== undefined
  2031. ? d3.quantize(interpolate, domain.length + 1)
  2032. : registry.get(key) === color
  2033. ? ordinalRange(scheme, domain.length + 1)
  2034. : undefined,
  2035. reverse
  2036. }
  2037. ) {
  2038. domain = arrayify(domain);
  2039. const sign = orderof(domain); // preserve descending domain
  2040. if (!isNaN(sign) && !isOrdered(domain, sign)) throw new Error(`the ${key} scale has a non-monotonic domain`);
  2041. if (reverse) range = d3.reverse(range); // domain ascending, so reverse range
  2042. return {
  2043. type: "threshold",
  2044. scale: d3.scaleThreshold(sign < 0 ? d3.reverse(domain) : domain, range === undefined ? [] : range).unknown(unknown),
  2045. domain,
  2046. range
  2047. };
  2048. }
  2049. function isOrdered(domain, sign) {
  2050. for (let i = 1, n = domain.length, d = domain[0]; i < n; ++i) {
  2051. const s = d3.descending(d, (d = domain[i]));
  2052. if (s !== 0 && s !== sign) return false;
  2053. }
  2054. return true;
  2055. }
  2056. // For non-numeric identity scales such as color and symbol, we can’t use D3’s
  2057. // identity scale because it coerces to number; and we can’t compute the domain
  2058. // (and equivalently range) since we can’t know whether the values are
  2059. // continuous or discrete.
  2060. function createScaleIdentity(key) {
  2061. return {type: "identity", scale: hasNumericRange(registry.get(key)) ? d3.scaleIdentity() : (d) => d};
  2062. }
  2063. function inferDomain$1(channels, f = finite$1) {
  2064. return channels.length
  2065. ? [
  2066. d3.min(channels, ({value}) => (value === undefined ? value : d3.min(value, f))),
  2067. d3.max(channels, ({value}) => (value === undefined ? value : d3.max(value, f)))
  2068. ]
  2069. : [0, 1];
  2070. }
  2071. function inferAutoDomain(key, channels) {
  2072. const type = registry.get(key);
  2073. return (type === radius || type === opacity || type === length ? inferZeroDomain : inferDomain$1)(channels);
  2074. }
  2075. function inferZeroDomain(channels) {
  2076. return [0, channels.length ? d3.max(channels, ({value}) => (value === undefined ? value : d3.max(value, finite$1))) : 1];
  2077. }
  2078. // We don’t want the upper bound of the radial domain to be zero, as this would
  2079. // be degenerate, so we ignore nonpositive values. We also don’t want the
  2080. // maximum default radius to exceed 30px.
  2081. function inferRadialRange(channels, domain) {
  2082. const hint = channels.find(({radius}) => radius !== undefined);
  2083. if (hint !== undefined) return [0, hint.radius]; // a natural maximum radius, e.g. hexbins
  2084. const h25 = d3.quantile(channels, 0.5, ({value}) => (value === undefined ? NaN : d3.quantile(value, 0.25, positive)));
  2085. const range = domain.map((d) => 3 * Math.sqrt(d / h25));
  2086. const k = 30 / d3.max(range);
  2087. return k < 1 ? range.map((r) => r * k) : range;
  2088. }
  2089. // We want a length scale’s domain to go from zero to a positive value, and to
  2090. // treat negative lengths if any as inverted vectors of equivalent magnitude. We
  2091. // also don’t want the maximum default length to exceed 60px.
  2092. function inferLengthRange(channels, domain) {
  2093. const h50 = d3.median(channels, ({value}) => (value === undefined ? NaN : d3.median(value, Math.abs)));
  2094. const range = domain.map((d) => (12 * d) / h50);
  2095. const k = 60 / d3.max(range);
  2096. return k < 1 ? range.map((r) => r * k) : range;
  2097. }
  2098. function inferLogDomain(channels) {
  2099. for (const {value} of channels) {
  2100. if (value !== undefined) {
  2101. for (let v of value) {
  2102. if (v > 0) return inferDomain$1(channels, positive);
  2103. if (v < 0) return inferDomain$1(channels, negative);
  2104. }
  2105. }
  2106. }
  2107. return [1, 10];
  2108. }
  2109. function inferQuantileDomain(channels) {
  2110. const domain = [];
  2111. for (const {value} of channels) {
  2112. if (value === undefined) continue;
  2113. for (const v of value) domain.push(v);
  2114. }
  2115. return domain;
  2116. }
  2117. function interpolatePiecewise(interpolate) {
  2118. return (i, j) => (t) => interpolate(i + t * (j - i));
  2119. }
  2120. let warnings = 0;
  2121. let lastMessage;
  2122. function consumeWarnings() {
  2123. const w = warnings;
  2124. warnings = 0;
  2125. lastMessage = undefined;
  2126. return w;
  2127. }
  2128. function warn(message) {
  2129. if (message === lastMessage) return;
  2130. lastMessage = message;
  2131. console.warn(message);
  2132. ++warnings;
  2133. }
  2134. function createScaleD(
  2135. key,
  2136. scale,
  2137. transform,
  2138. channels,
  2139. {
  2140. type,
  2141. nice,
  2142. clamp,
  2143. domain = inferDomain$1(channels),
  2144. unknown,
  2145. pivot = 0,
  2146. scheme,
  2147. range,
  2148. symmetric = true,
  2149. interpolate = registry.get(key) === color
  2150. ? scheme == null && range !== undefined
  2151. ? d3.interpolateRgb
  2152. : quantitativeScheme(scheme !== undefined ? scheme : "rdbu")
  2153. : d3.interpolateNumber,
  2154. reverse
  2155. }
  2156. ) {
  2157. pivot = +pivot;
  2158. domain = arrayify(domain);
  2159. let [min, max] = domain;
  2160. if (domain.length > 2) warn(`Warning: the diverging ${key} scale domain contains extra elements.`);
  2161. if (d3.descending(min, max) < 0) ([min, max] = [max, min]), (reverse = !reverse);
  2162. min = Math.min(min, pivot);
  2163. max = Math.max(max, pivot);
  2164. // Sometimes interpolate is a named interpolator, such as "lab" for Lab color
  2165. // space. Other times interpolate is a function that takes two arguments and
  2166. // is used in conjunction with the range. And other times the interpolate
  2167. // function is a “fixed” interpolator on the [0, 1] interval, as when a
  2168. // color scheme such as interpolateRdBu is used.
  2169. if (typeof interpolate !== "function") {
  2170. interpolate = maybeInterpolator(interpolate);
  2171. }
  2172. // If an explicit range is specified, promote it to a piecewise interpolator.
  2173. if (range !== undefined) {
  2174. interpolate =
  2175. interpolate.length === 1 ? interpolatePiecewise(interpolate)(...range) : d3.piecewise(interpolate, range);
  2176. }
  2177. // Reverse before normalization.
  2178. if (reverse) interpolate = flip(interpolate);
  2179. // Normalize the interpolator for symmetric difference around the pivot.
  2180. if (symmetric) {
  2181. const mid = transform.apply(pivot);
  2182. const mindelta = mid - transform.apply(min);
  2183. const maxdelta = transform.apply(max) - mid;
  2184. if (mindelta < maxdelta) min = transform.invert(mid - maxdelta);
  2185. else if (mindelta > maxdelta) max = transform.invert(mid + mindelta);
  2186. }
  2187. scale.domain([min, pivot, max]).unknown(unknown).interpolator(interpolate);
  2188. if (clamp) scale.clamp(clamp);
  2189. if (nice) scale.nice(nice);
  2190. return {type, domain: [min, max], pivot, interpolate, scale};
  2191. }
  2192. function createScaleDiverging(key, channels, options) {
  2193. return createScaleD(key, d3.scaleDiverging(), transformIdentity, channels, options);
  2194. }
  2195. function createScaleDivergingSqrt(key, channels, options) {
  2196. return createScaleDivergingPow(key, channels, {...options, exponent: 0.5});
  2197. }
  2198. function createScaleDivergingPow(key, channels, {exponent = 1, ...options}) {
  2199. return createScaleD(key, d3.scaleDivergingPow().exponent((exponent = +exponent)), transformPow(exponent), channels, {
  2200. ...options,
  2201. type: "diverging-pow"
  2202. });
  2203. }
  2204. function createScaleDivergingLog(
  2205. key,
  2206. channels,
  2207. {base = 10, pivot = 1, domain = inferDomain$1(channels, pivot < 0 ? negative : positive), ...options}
  2208. ) {
  2209. return createScaleD(key, d3.scaleDivergingLog().base((base = +base)), transformLog, channels, {
  2210. domain,
  2211. pivot,
  2212. ...options
  2213. });
  2214. }
  2215. function createScaleDivergingSymlog(key, channels, {constant = 1, ...options}) {
  2216. return createScaleD(
  2217. key,
  2218. d3.scaleDivergingSymlog().constant((constant = +constant)),
  2219. transformSymlog(constant),
  2220. channels,
  2221. options
  2222. );
  2223. }
  2224. const transformIdentity = {
  2225. apply(x) {
  2226. return x;
  2227. },
  2228. invert(x) {
  2229. return x;
  2230. }
  2231. };
  2232. const transformLog = {
  2233. apply: Math.log,
  2234. invert: Math.exp
  2235. };
  2236. const transformSqrt = {
  2237. apply(x) {
  2238. return Math.sign(x) * Math.sqrt(Math.abs(x));
  2239. },
  2240. invert(x) {
  2241. return Math.sign(x) * (x * x);
  2242. }
  2243. };
  2244. function transformPow(exponent) {
  2245. return exponent === 0.5
  2246. ? transformSqrt
  2247. : {
  2248. apply(x) {
  2249. return Math.sign(x) * Math.pow(Math.abs(x), exponent);
  2250. },
  2251. invert(x) {
  2252. return Math.sign(x) * Math.pow(Math.abs(x), 1 / exponent);
  2253. }
  2254. };
  2255. }
  2256. function transformSymlog(constant) {
  2257. return {
  2258. apply(x) {
  2259. return Math.sign(x) * Math.log1p(Math.abs(x / constant));
  2260. },
  2261. invert(x) {
  2262. return Math.sign(x) * Math.expm1(Math.abs(x)) * constant;
  2263. }
  2264. };
  2265. }
  2266. function createScaleT(key, scale, channels, options) {
  2267. return createScaleQ(key, scale, channels, options);
  2268. }
  2269. function createScaleTime(key, channels, options) {
  2270. return createScaleT(key, d3.scaleTime(), channels, options);
  2271. }
  2272. function createScaleUtc(key, channels, options) {
  2273. return createScaleT(key, d3.scaleUtc(), channels, options);
  2274. }
  2275. // This denotes an implicitly ordinal color scale: the scale type was not set,
  2276. // but the associated values are strings or booleans. If the associated defined
  2277. // values are entirely boolean, the range will default to greys. You can opt out
  2278. // of this by setting the type explicitly.
  2279. const ordinalImplicit = Symbol("ordinal");
  2280. function createScaleO(key, scale, channels, {type, interval, domain, range, reverse, hint}) {
  2281. interval = maybeRangeInterval(interval, type);
  2282. if (domain === undefined) domain = inferDomain(channels, interval, key);
  2283. if (type === "categorical" || type === ordinalImplicit) type = "ordinal"; // shorthand for color schemes
  2284. if (reverse) domain = d3.reverse(domain);
  2285. domain = scale.domain(domain).domain(); // deduplicate
  2286. if (range !== undefined) {
  2287. // If the range is specified as a function, pass it the domain.
  2288. if (typeof range === "function") range = range(domain);
  2289. scale.range(range);
  2290. }
  2291. return {type, domain, range, scale, hint, interval};
  2292. }
  2293. function createScaleOrdinal(key, channels, {type, interval, domain, range, scheme, unknown, ...options}) {
  2294. interval = maybeRangeInterval(interval, type);
  2295. if (domain === undefined) domain = inferDomain(channels, interval, key);
  2296. let hint;
  2297. if (registry.get(key) === symbol) {
  2298. hint = inferSymbolHint(channels);
  2299. range = range === undefined ? inferSymbolRange(hint) : map$1(range, maybeSymbol);
  2300. } else if (registry.get(key) === color) {
  2301. if (range === undefined && (type === "ordinal" || type === ordinalImplicit)) {
  2302. range = maybeBooleanRange(domain, scheme);
  2303. if (range !== undefined) scheme = undefined; // Don’t re-apply scheme.
  2304. }
  2305. if (scheme === undefined && range === undefined) {
  2306. scheme = type === "ordinal" ? "turbo" : "observable10";
  2307. }
  2308. if (scheme !== undefined) {
  2309. if (range !== undefined) {
  2310. const interpolate = quantitativeScheme(scheme);
  2311. const t0 = range[0],
  2312. d = range[1] - range[0];
  2313. range = ({length: n}) => d3.quantize((t) => interpolate(t0 + d * t), n);
  2314. } else {
  2315. range = ordinalScheme(scheme);
  2316. }
  2317. }
  2318. }
  2319. if (unknown === d3.scaleImplicit) {
  2320. throw new Error(`implicit unknown on ${key} scale is not supported`);
  2321. }
  2322. return createScaleO(key, d3.scaleOrdinal().unknown(unknown), channels, {...options, type, domain, range, hint});
  2323. }
  2324. function createScalePoint(key, channels, {align = 0.5, padding = 0.5, ...options}) {
  2325. return maybeRound$1(d3.scalePoint().align(align).padding(padding), channels, options, key);
  2326. }
  2327. function createScaleBand(
  2328. key,
  2329. channels,
  2330. {
  2331. align = 0.5,
  2332. padding = 0.1,
  2333. paddingInner = padding,
  2334. paddingOuter = key === "fx" || key === "fy" ? 0 : padding,
  2335. ...options
  2336. }
  2337. ) {
  2338. return maybeRound$1(
  2339. d3.scaleBand().align(align).paddingInner(paddingInner).paddingOuter(paddingOuter),
  2340. channels,
  2341. options,
  2342. key
  2343. );
  2344. }
  2345. function maybeRound$1(scale, channels, options, key) {
  2346. let {round} = options;
  2347. if (round !== undefined) scale.round((round = !!round));
  2348. scale = createScaleO(key, scale, channels, options);
  2349. scale.round = round; // preserve for autoScaleRound
  2350. return scale;
  2351. }
  2352. function inferDomain(channels, interval, key) {
  2353. const values = new d3.InternSet();
  2354. for (const {value, domain} of channels) {
  2355. if (domain !== undefined) return domain(); // see channelDomain
  2356. if (value === undefined) continue;
  2357. for (const v of value) values.add(v);
  2358. }
  2359. if (interval !== undefined) {
  2360. const [min, max] = d3.extent(values).map(interval.floor, interval);
  2361. return interval.range(min, interval.offset(max));
  2362. }
  2363. if (values.size > 10e3 && registry.get(key) === position$1) {
  2364. throw new Error(`implicit ordinal domain of ${key} scale has more than 10,000 values`);
  2365. }
  2366. return d3.sort(values, ascendingDefined);
  2367. }
  2368. // If all channels provide a consistent hint, propagate it to the scale.
  2369. function inferHint(channels, key) {
  2370. let value;
  2371. for (const {hint} of channels) {
  2372. const candidate = hint?.[key];
  2373. if (candidate === undefined) continue; // no hint here
  2374. if (value === undefined) value = candidate; // first hint
  2375. else if (value !== candidate) return; // inconsistent hint
  2376. }
  2377. return value;
  2378. }
  2379. function inferSymbolHint(channels) {
  2380. return {
  2381. fill: inferHint(channels, "fill"),
  2382. stroke: inferHint(channels, "stroke")
  2383. };
  2384. }
  2385. function inferSymbolRange(hint) {
  2386. return isNoneish(hint.fill) ? d3.symbolsStroke : d3.symbolsFill;
  2387. }
  2388. function createScales(
  2389. channelsByScale,
  2390. {
  2391. label: globalLabel,
  2392. inset: globalInset = 0,
  2393. insetTop: globalInsetTop = globalInset,
  2394. insetRight: globalInsetRight = globalInset,
  2395. insetBottom: globalInsetBottom = globalInset,
  2396. insetLeft: globalInsetLeft = globalInset,
  2397. round,
  2398. nice,
  2399. clamp,
  2400. zero,
  2401. align,
  2402. padding,
  2403. projection,
  2404. facet: {label: facetLabel = globalLabel} = {},
  2405. ...options
  2406. } = {}
  2407. ) {
  2408. const scales = {};
  2409. for (const [key, channels] of channelsByScale) {
  2410. const scaleOptions = options[key];
  2411. const scale = createScale(key, channels, {
  2412. round: registry.get(key) === position$1 ? round : undefined, // only for position
  2413. nice,
  2414. clamp,
  2415. zero,
  2416. align,
  2417. padding,
  2418. projection,
  2419. ...scaleOptions
  2420. });
  2421. if (scale) {
  2422. // populate generic scale options (percent, transform, insets)
  2423. let {
  2424. label = key === "fx" || key === "fy" ? facetLabel : globalLabel,
  2425. percent,
  2426. transform,
  2427. inset,
  2428. insetTop = inset !== undefined ? inset : key === "y" ? globalInsetTop : 0, // not fy
  2429. insetRight = inset !== undefined ? inset : key === "x" ? globalInsetRight : 0, // not fx
  2430. insetBottom = inset !== undefined ? inset : key === "y" ? globalInsetBottom : 0, // not fy
  2431. insetLeft = inset !== undefined ? inset : key === "x" ? globalInsetLeft : 0 // not fx
  2432. } = scaleOptions || {};
  2433. if (transform == null) transform = undefined;
  2434. else if (typeof transform !== "function") throw new Error("invalid scale transform; not a function");
  2435. scale.percent = !!percent;
  2436. scale.label = label === undefined ? inferScaleLabel(channels, scale) : label;
  2437. scale.transform = transform;
  2438. if (key === "x" || key === "fx") {
  2439. scale.insetLeft = +insetLeft;
  2440. scale.insetRight = +insetRight;
  2441. } else if (key === "y" || key === "fy") {
  2442. scale.insetTop = +insetTop;
  2443. scale.insetBottom = +insetBottom;
  2444. }
  2445. scales[key] = scale;
  2446. }
  2447. }
  2448. return scales;
  2449. }
  2450. function createScaleFunctions(descriptors) {
  2451. const scales = {};
  2452. const scaleFunctions = {scales};
  2453. for (const [key, descriptor] of Object.entries(descriptors)) {
  2454. const {scale, type, interval, label} = descriptor;
  2455. scales[key] = exposeScale(descriptor);
  2456. scaleFunctions[key] = scale;
  2457. // TODO: pass these properties, which are needed for axes, in the descriptor.
  2458. scale.type = type;
  2459. if (interval != null) scale.interval = interval;
  2460. if (label != null) scale.label = label;
  2461. }
  2462. return scaleFunctions;
  2463. }
  2464. // Mutates scale.range!
  2465. function autoScaleRange(scales, dimensions) {
  2466. const {x, y, fx, fy} = scales;
  2467. const superdimensions = fx || fy ? outerDimensions(dimensions) : dimensions;
  2468. if (fx) autoScaleRangeX(fx, superdimensions);
  2469. if (fy) autoScaleRangeY(fy, superdimensions);
  2470. const subdimensions = fx || fy ? innerDimensions(scales, dimensions) : dimensions;
  2471. if (x) autoScaleRangeX(x, subdimensions);
  2472. if (y) autoScaleRangeY(y, subdimensions);
  2473. }
  2474. // Channels can have labels; if all the channels for a given scale are
  2475. // consistently labeled (i.e., have the same value if not undefined), and the
  2476. // corresponding scale doesn’t already have an explicit label, then the
  2477. // channels’ label is promoted to the scale. This inferred label should have an
  2478. // orientation-appropriate arrow added when used as an axis, but we don’t want
  2479. // to add the arrow when the label is set explicitly as an option; so, the
  2480. // inferred label is distinguished as an object with an “inferred” property.
  2481. function inferScaleLabel(channels = [], scale) {
  2482. let label;
  2483. for (const {label: l} of channels) {
  2484. if (l === undefined) continue;
  2485. if (label === undefined) label = l;
  2486. else if (label !== l) return;
  2487. }
  2488. if (label === undefined) return;
  2489. if (!isOrdinalScale(scale) && scale.percent) label = `${label} (%)`;
  2490. return {inferred: true, toString: () => label};
  2491. }
  2492. // Determines whether the scale points in the “positive” (right or down) or
  2493. // “negative” (left or up) direction; if the scale order cannot be determined,
  2494. // returns NaN; used to assign an appropriate label arrow.
  2495. function inferScaleOrder(scale) {
  2496. return Math.sign(orderof(scale.domain())) * Math.sign(orderof(scale.range()));
  2497. }
  2498. // Returns the dimensions of the outer frame; this is subdivided into facets
  2499. // with the margins of each facet collapsing into the outer margins.
  2500. function outerDimensions(dimensions) {
  2501. const {
  2502. marginTop,
  2503. marginRight,
  2504. marginBottom,
  2505. marginLeft,
  2506. width,
  2507. height,
  2508. facet: {
  2509. marginTop: facetMarginTop,
  2510. marginRight: facetMarginRight,
  2511. marginBottom: facetMarginBottom,
  2512. marginLeft: facetMarginLeft
  2513. }
  2514. } = dimensions;
  2515. return {
  2516. marginTop: Math.max(marginTop, facetMarginTop),
  2517. marginRight: Math.max(marginRight, facetMarginRight),
  2518. marginBottom: Math.max(marginBottom, facetMarginBottom),
  2519. marginLeft: Math.max(marginLeft, facetMarginLeft),
  2520. width,
  2521. height
  2522. };
  2523. }
  2524. // Returns the dimensions of each facet.
  2525. function innerDimensions({fx, fy}, dimensions) {
  2526. const {marginTop, marginRight, marginBottom, marginLeft, width, height} = outerDimensions(dimensions);
  2527. return {
  2528. marginTop,
  2529. marginRight,
  2530. marginBottom,
  2531. marginLeft,
  2532. width: fx ? fx.scale.bandwidth() + marginLeft + marginRight : width,
  2533. height: fy ? fy.scale.bandwidth() + marginTop + marginBottom : height,
  2534. facet: {width, height}
  2535. };
  2536. }
  2537. function autoScaleRangeX(scale, dimensions) {
  2538. if (scale.range === undefined) {
  2539. const {insetLeft, insetRight} = scale;
  2540. const {width, marginLeft = 0, marginRight = 0} = dimensions;
  2541. const left = marginLeft + insetLeft;
  2542. const right = width - marginRight - insetRight;
  2543. scale.range = [left, Math.max(left, right)];
  2544. if (!isOrdinalScale(scale)) scale.range = piecewiseRange(scale);
  2545. scale.scale.range(scale.range);
  2546. }
  2547. autoScaleRound(scale);
  2548. }
  2549. function autoScaleRangeY(scale, dimensions) {
  2550. if (scale.range === undefined) {
  2551. const {insetTop, insetBottom} = scale;
  2552. const {height, marginTop = 0, marginBottom = 0} = dimensions;
  2553. const top = marginTop + insetTop;
  2554. const bottom = height - marginBottom - insetBottom;
  2555. scale.range = [Math.max(top, bottom), top];
  2556. if (!isOrdinalScale(scale)) scale.range = piecewiseRange(scale);
  2557. else scale.range.reverse();
  2558. scale.scale.range(scale.range);
  2559. }
  2560. autoScaleRound(scale);
  2561. }
  2562. function autoScaleRound(scale) {
  2563. if (scale.round === undefined && isBandScale(scale) && roundError(scale) <= 30) {
  2564. scale.scale.round(true);
  2565. }
  2566. }
  2567. // If we were to turn on rounding for this band or point scale, how much wasted
  2568. // space would it introduce (on both ends of the range)? This must match
  2569. // d3.scaleBand’s rounding behavior:
  2570. // https://github.com/d3/d3-scale/blob/83555bd759c7314420bd4240642beda5e258db9e/src/band.js#L20-L32
  2571. function roundError({scale}) {
  2572. const n = scale.domain().length;
  2573. const [start, stop] = scale.range();
  2574. const paddingInner = scale.paddingInner ? scale.paddingInner() : 1;
  2575. const paddingOuter = scale.paddingOuter ? scale.paddingOuter() : scale.padding();
  2576. const m = n - paddingInner;
  2577. const step = Math.abs(stop - start) / Math.max(1, m + paddingOuter * 2);
  2578. return (step - Math.floor(step)) * m;
  2579. }
  2580. function piecewiseRange(scale) {
  2581. const length = scale.scale.domain().length + isThresholdScale(scale);
  2582. if (!(length > 2)) return scale.range;
  2583. const [start, end] = scale.range;
  2584. return Array.from({length}, (_, i) => start + (i / (length - 1)) * (end - start));
  2585. }
  2586. function normalizeScale(key, scale, hint) {
  2587. return createScale(key, hint === undefined ? undefined : [{hint}], {...scale});
  2588. }
  2589. function createScale(key, channels = [], options = {}) {
  2590. const type = inferScaleType(key, channels, options);
  2591. // Warn for common misuses of implicit ordinal scales. We disable this test if
  2592. // you specify a scale interval or if you set the domain or range explicitly,
  2593. // since setting the domain or range (typically with a cardinality of more than
  2594. // two) is another indication that you intended for the scale to be ordinal; we
  2595. // also disable it for facet scales since these are always band scales.
  2596. if (
  2597. options.type === undefined &&
  2598. options.domain === undefined &&
  2599. options.range === undefined &&
  2600. options.interval == null &&
  2601. key !== "fx" &&
  2602. key !== "fy" &&
  2603. isOrdinalScale({type})
  2604. ) {
  2605. const values = channels.map(({value}) => value).filter((value) => value !== undefined);
  2606. if (values.some(isTemporal))
  2607. warn(
  2608. `Warning: some data associated with the ${key} scale are dates. Dates are typically associated with a "utc" or "time" scale rather than a "${formatScaleType(
  2609. type
  2610. )}" scale. If you are using a bar mark, you probably want a rect mark with the interval option instead; if you are using a group transform, you probably want a bin transform instead. If you want to treat this data as ordinal, you can specify the interval of the ${key} scale (e.g., d3.utcDay), or you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType(
  2611. type
  2612. )}".`
  2613. );
  2614. else if (values.some(isTemporalString))
  2615. warn(
  2616. `Warning: some data associated with the ${key} scale are strings that appear to be dates (e.g., YYYY-MM-DD). If these strings represent dates, you should parse them to Date objects. Dates are typically associated with a "utc" or "time" scale rather than a "${formatScaleType(
  2617. type
  2618. )}" scale. If you are using a bar mark, you probably want a rect mark with the interval option instead; if you are using a group transform, you probably want a bin transform instead. If you want to treat this data as ordinal, you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType(
  2619. type
  2620. )}".`
  2621. );
  2622. else if (values.some(isNumericString))
  2623. warn(
  2624. `Warning: some data associated with the ${key} scale are strings that appear to be numbers. If these strings represent numbers, you should parse or coerce them to numbers. Numbers are typically associated with a "linear" scale rather than a "${formatScaleType(
  2625. type
  2626. )}" scale. If you want to treat this data as ordinal, you can specify the interval of the ${key} scale (e.g., 1 for integers), or you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType(
  2627. type
  2628. )}".`
  2629. );
  2630. }
  2631. options.type = type; // Mutates input!
  2632. // Once the scale type is known, coerce the associated channel values and any
  2633. // explicitly-specified domain to the expected type.
  2634. switch (type) {
  2635. case "diverging":
  2636. case "diverging-sqrt":
  2637. case "diverging-pow":
  2638. case "diverging-log":
  2639. case "diverging-symlog":
  2640. case "cyclical":
  2641. case "sequential":
  2642. case "linear":
  2643. case "sqrt":
  2644. case "threshold":
  2645. case "quantile":
  2646. case "pow":
  2647. case "log":
  2648. case "symlog":
  2649. options = coerceType(channels, options, coerceNumbers);
  2650. break;
  2651. case "identity":
  2652. switch (registry.get(key)) {
  2653. case position$1:
  2654. options = coerceType(channels, options, coerceNumbers);
  2655. break;
  2656. case symbol:
  2657. options = coerceType(channels, options, coerceSymbols);
  2658. break;
  2659. }
  2660. break;
  2661. case "utc":
  2662. case "time":
  2663. options = coerceType(channels, options, coerceDates);
  2664. break;
  2665. }
  2666. switch (type) {
  2667. case "diverging":
  2668. return createScaleDiverging(key, channels, options);
  2669. case "diverging-sqrt":
  2670. return createScaleDivergingSqrt(key, channels, options);
  2671. case "diverging-pow":
  2672. return createScaleDivergingPow(key, channels, options);
  2673. case "diverging-log":
  2674. return createScaleDivergingLog(key, channels, options);
  2675. case "diverging-symlog":
  2676. return createScaleDivergingSymlog(key, channels, options);
  2677. case "categorical":
  2678. case "ordinal":
  2679. case ordinalImplicit:
  2680. return createScaleOrdinal(key, channels, options);
  2681. case "cyclical":
  2682. case "sequential":
  2683. case "linear":
  2684. return createScaleLinear(key, channels, options);
  2685. case "sqrt":
  2686. return createScaleSqrt(key, channels, options);
  2687. case "threshold":
  2688. return createScaleThreshold(key, channels, options);
  2689. case "quantile":
  2690. return createScaleQuantile(key, channels, options);
  2691. case "quantize":
  2692. return createScaleQuantize(key, channels, options);
  2693. case "pow":
  2694. return createScalePow(key, channels, options);
  2695. case "log":
  2696. return createScaleLog(key, channels, options);
  2697. case "symlog":
  2698. return createScaleSymlog(key, channels, options);
  2699. case "utc":
  2700. return createScaleUtc(key, channels, options);
  2701. case "time":
  2702. return createScaleTime(key, channels, options);
  2703. case "point":
  2704. return createScalePoint(key, channels, options);
  2705. case "band":
  2706. return createScaleBand(key, channels, options);
  2707. case "identity":
  2708. return createScaleIdentity(key);
  2709. case undefined:
  2710. return;
  2711. default:
  2712. throw new Error(`unknown scale type: ${type}`);
  2713. }
  2714. }
  2715. function formatScaleType(type) {
  2716. return typeof type === "symbol" ? type.description : type;
  2717. }
  2718. function maybeScaleType(type) {
  2719. return typeof type === "string" ? `${type}`.toLowerCase() : type;
  2720. }
  2721. // A special type symbol when the x and y scales are replaced with a projection.
  2722. const typeProjection = {toString: () => "projection"};
  2723. function inferScaleType(key, channels, {type, domain, range, scheme, pivot, projection}) {
  2724. type = maybeScaleType(type);
  2725. // The facet scales are always band scales; this cannot be changed.
  2726. if (key === "fx" || key === "fy") return "band";
  2727. // If a projection is specified, the x- and y-scales are disabled; these
  2728. // channels will be projected rather than scaled. (But still check that none
  2729. // of the associated channels are incompatible with a projection.)
  2730. if ((key === "x" || key === "y") && projection != null) type = typeProjection;
  2731. // If a channel dictates a scale type, make sure that it is consistent with
  2732. // the user-specified scale type (if any) and all other channels. For example,
  2733. // barY requires x to be a band scale and disallows any other scale type.
  2734. for (const channel of channels) {
  2735. const t = maybeScaleType(channel.type);
  2736. if (t === undefined) continue;
  2737. else if (type === undefined) type = t;
  2738. else if (type !== t) throw new Error(`scale incompatible with channel: ${type} !== ${t}`);
  2739. }
  2740. // If the scale, a channel, or user specified a (consistent) type, return it.
  2741. if (type === typeProjection) return;
  2742. if (type !== undefined) return type;
  2743. // If there’s no data (and no type) associated with this scale, don’t create a scale.
  2744. if (domain === undefined && !channels.some(({value}) => value !== undefined)) return;
  2745. // Some scales have default types.
  2746. const kind = registry.get(key);
  2747. if (kind === radius) return "sqrt";
  2748. if (kind === opacity || kind === length) return "linear";
  2749. if (kind === symbol) return "ordinal";
  2750. // If a domain or range is explicitly specified and doesn’t have two values,
  2751. // assume it’s ordinal. You can still use a “piecewise” (or “polylinear”)
  2752. // scale, but you must set the type explicitly.
  2753. const n = (domain ?? range)?.length;
  2754. if (n < 2 || n > 2) return asOrdinalType(kind);
  2755. // Otherwise, infer the scale type from the data! Prefer the domain, if
  2756. // present, over channels. (The domain and channels should be consistently
  2757. // typed, and the domain is more explicit and typically much smaller.) We only
  2758. // check the first defined value for expedience and simplicity; we expect
  2759. // that the types are consistent.
  2760. if (domain !== undefined) {
  2761. if (isOrdinal(domain)) return asOrdinalType(kind);
  2762. if (isTemporal(domain)) return "utc";
  2763. } else {
  2764. const values = channels.map(({value}) => value).filter((value) => value !== undefined);
  2765. if (values.some(isOrdinal)) return asOrdinalType(kind);
  2766. if (values.some(isTemporal)) return "utc";
  2767. }
  2768. // For color scales, take a hint from the color scheme and pivot option.
  2769. if (kind === color) {
  2770. if (pivot != null || isDivergingScheme(scheme)) return "diverging";
  2771. if (isCategoricalScheme(scheme)) return "categorical";
  2772. }
  2773. return "linear";
  2774. }
  2775. // Positional scales default to a point scale instead of an ordinal scale.
  2776. function asOrdinalType(kind) {
  2777. switch (kind) {
  2778. case position$1:
  2779. return "point";
  2780. case color:
  2781. return ordinalImplicit;
  2782. default:
  2783. return "ordinal";
  2784. }
  2785. }
  2786. function isOrdinalScale({type}) {
  2787. return type === "ordinal" || type === "point" || type === "band" || type === ordinalImplicit;
  2788. }
  2789. function isThresholdScale({type}) {
  2790. return type === "threshold";
  2791. }
  2792. function isBandScale({type}) {
  2793. return type === "point" || type === "band";
  2794. }
  2795. // Certain marks have special behavior if a scale is collapsed, i.e. if the
  2796. // domain is degenerate and represents only a single value such as [3, 3]; for
  2797. // example, a rect will span the full extent of the chart along a collapsed
  2798. // dimension (whereas a dot will simply be drawn in the center).
  2799. function isCollapsed(scale) {
  2800. if (scale === undefined) return true; // treat missing scale as collapsed
  2801. const domain = scale.domain();
  2802. const value = scale(domain[0]);
  2803. for (let i = 1, n = domain.length; i < n; ++i) {
  2804. if (scale(domain[i]) - value) {
  2805. return false;
  2806. }
  2807. }
  2808. return true;
  2809. }
  2810. // Mutates channel.value!
  2811. function coerceType(channels, {domain, ...options}, coerceValues) {
  2812. for (const c of channels) {
  2813. if (c.value !== undefined) {
  2814. if (domain === undefined) domain = c.value?.domain; // promote channel domain
  2815. c.value = coerceValues(c.value);
  2816. }
  2817. }
  2818. return {
  2819. domain: domain === undefined ? domain : coerceValues(domain),
  2820. ...options
  2821. };
  2822. }
  2823. function coerceSymbols(values) {
  2824. return map$1(values, maybeSymbol);
  2825. }
  2826. function scale(options = {}) {
  2827. let scale;
  2828. for (const key in options) {
  2829. if (!registry.has(key)) continue; // ignore unknown properties
  2830. if (!isScaleOptions(options[key])) continue; // e.g., ignore {color: "red"}
  2831. if (scale !== undefined) throw new Error("ambiguous scale definition; multiple scales found");
  2832. scale = exposeScale(normalizeScale(key, options[key]));
  2833. }
  2834. if (scale === undefined) throw new Error("invalid scale definition; no scale found");
  2835. return scale;
  2836. }
  2837. function exposeScales(scales) {
  2838. return (key) => {
  2839. if (!registry.has((key = `${key}`))) throw new Error(`unknown scale: ${key}`);
  2840. return scales[key];
  2841. };
  2842. }
  2843. // Note: axis- and legend-related properties (such as label, ticks and
  2844. // tickFormat) are not included here as they do not affect the scale’s behavior.
  2845. function exposeScale({scale, type, domain, range, interpolate, interval, transform, percent, pivot}) {
  2846. if (type === "identity") return {type: "identity", apply: (d) => d, invert: (d) => d};
  2847. const unknown = scale.unknown ? scale.unknown() : undefined;
  2848. return {
  2849. type,
  2850. domain: slice(domain), // defensive copy
  2851. ...(range !== undefined && {range: slice(range)}), // defensive copy
  2852. ...(transform !== undefined && {transform}),
  2853. ...(percent && {percent}), // only exposed if truthy
  2854. ...(unknown !== undefined && {unknown}),
  2855. ...(interval !== undefined && {interval}),
  2856. // quantitative
  2857. ...(interpolate !== undefined && {interpolate}),
  2858. ...(scale.clamp && {clamp: scale.clamp()}),
  2859. // diverging (always asymmetric; we never want to apply the symmetric transform twice)
  2860. ...(pivot !== undefined && {pivot, symmetric: false}),
  2861. // log, diverging-log
  2862. ...(scale.base && {base: scale.base()}),
  2863. // pow, diverging-pow
  2864. ...(scale.exponent && {exponent: scale.exponent()}),
  2865. // symlog, diverging-symlog
  2866. ...(scale.constant && {constant: scale.constant()}),
  2867. // band, point
  2868. ...(scale.align && {align: scale.align(), round: scale.round()}),
  2869. ...(scale.padding &&
  2870. (scale.paddingInner
  2871. ? {paddingInner: scale.paddingInner(), paddingOuter: scale.paddingOuter()}
  2872. : {padding: scale.padding()})),
  2873. ...(scale.bandwidth && {bandwidth: scale.bandwidth(), step: scale.step()}),
  2874. // utilities
  2875. apply: (t) => scale(t),
  2876. ...(scale.invert && {invert: (t) => scale.invert(t)})
  2877. };
  2878. }
  2879. // Returns an array of {x?, y?, i} objects representing the facet domain.
  2880. function createFacets(channelsByScale, options) {
  2881. const {fx, fy} = createScales(channelsByScale, options);
  2882. const fxDomain = fx?.scale.domain();
  2883. const fyDomain = fy?.scale.domain();
  2884. return fxDomain && fyDomain
  2885. ? d3.cross(fxDomain, fyDomain).map(([x, y], i) => ({x, y, i}))
  2886. : fxDomain
  2887. ? fxDomain.map((x, i) => ({x, i}))
  2888. : fyDomain
  2889. ? fyDomain.map((y, i) => ({y, i}))
  2890. : undefined;
  2891. }
  2892. function recreateFacets(facets, {x: X, y: Y}) {
  2893. X &&= facetIndex(X);
  2894. Y &&= facetIndex(Y);
  2895. return facets
  2896. .filter(
  2897. X && Y // remove any facets no longer present in the domain
  2898. ? (f) => X.has(f.x) && Y.has(f.y)
  2899. : X
  2900. ? (f) => X.has(f.x)
  2901. : (f) => Y.has(f.y)
  2902. )
  2903. .sort(
  2904. X && Y // reorder facets to match the new scale domains
  2905. ? (a, b) => X.get(a.x) - X.get(b.x) || Y.get(a.y) - Y.get(b.y)
  2906. : X
  2907. ? (a, b) => X.get(a.x) - X.get(b.x)
  2908. : (a, b) => Y.get(a.y) - Y.get(b.y)
  2909. );
  2910. }
  2911. // Returns a (possibly nested) Map of [[key1, index1], [key2, index2], …]
  2912. // representing the data indexes associated with each facet.
  2913. function facetGroups(data, {fx, fy}) {
  2914. const I = range(data);
  2915. const FX = fx?.value;
  2916. const FY = fy?.value;
  2917. return fx && fy
  2918. ? d3.rollup(
  2919. I,
  2920. (G) => ((G.fx = FX[G[0]]), (G.fy = FY[G[0]]), G),
  2921. (i) => FX[i],
  2922. (i) => FY[i]
  2923. )
  2924. : fx
  2925. ? d3.rollup(
  2926. I,
  2927. (G) => ((G.fx = FX[G[0]]), G),
  2928. (i) => FX[i]
  2929. )
  2930. : d3.rollup(
  2931. I,
  2932. (G) => ((G.fy = FY[G[0]]), G),
  2933. (i) => FY[i]
  2934. );
  2935. }
  2936. function facetTranslator(fx, fy, {marginTop, marginLeft}) {
  2937. const x = fx ? ({x}) => fx(x) - marginLeft : () => 0;
  2938. const y = fy ? ({y}) => fy(y) - marginTop : () => 0;
  2939. return function (d) {
  2940. if (this.tagName === "svg") {
  2941. this.setAttribute("x", x(d));
  2942. this.setAttribute("y", y(d));
  2943. } else {
  2944. this.setAttribute("transform", `translate(${x(d)},${y(d)})`);
  2945. }
  2946. };
  2947. }
  2948. // Returns an index that for each facet lists all the elements present in other
  2949. // facets in the original index. TODO Memoize to avoid repeated work?
  2950. function facetExclude(index) {
  2951. const ex = [];
  2952. const e = new Uint32Array(d3.sum(index, (d) => d.length));
  2953. for (const i of index) {
  2954. let n = 0;
  2955. for (const j of index) {
  2956. if (i === j) continue;
  2957. e.set(j, n);
  2958. n += j.length;
  2959. }
  2960. ex.push(e.slice(0, n));
  2961. }
  2962. return ex;
  2963. }
  2964. const facetAnchors = new Map([
  2965. ["top", facetAnchorTop],
  2966. ["right", facetAnchorRight],
  2967. ["bottom", facetAnchorBottom],
  2968. ["left", facetAnchorLeft],
  2969. ["top-left", and(facetAnchorTop, facetAnchorLeft)],
  2970. ["top-right", and(facetAnchorTop, facetAnchorRight)],
  2971. ["bottom-left", and(facetAnchorBottom, facetAnchorLeft)],
  2972. ["bottom-right", and(facetAnchorBottom, facetAnchorRight)],
  2973. ["top-empty", facetAnchorTopEmpty],
  2974. ["right-empty", facetAnchorRightEmpty],
  2975. ["bottom-empty", facetAnchorBottomEmpty],
  2976. ["left-empty", facetAnchorLeftEmpty],
  2977. ["empty", facetAnchorEmpty]
  2978. ]);
  2979. function maybeFacetAnchor(facetAnchor) {
  2980. if (facetAnchor == null) return null;
  2981. const anchor = facetAnchors.get(`${facetAnchor}`.toLowerCase());
  2982. if (anchor) return anchor;
  2983. throw new Error(`invalid facet anchor: ${facetAnchor}`);
  2984. }
  2985. const indexCache = new WeakMap();
  2986. function facetIndex(V) {
  2987. let I = indexCache.get(V);
  2988. if (!I) indexCache.set(V, (I = new d3.InternMap(map$1(V, (v, i) => [v, i]))));
  2989. return I;
  2990. }
  2991. // Like V.indexOf(v), but with the same semantics as InternMap.
  2992. function facetIndexOf(V, v) {
  2993. return facetIndex(V).get(v);
  2994. }
  2995. // Like facets.find, but with the same semantics as InternMap.
  2996. function facetFind(facets, x, y) {
  2997. x = keyof(x);
  2998. y = keyof(y);
  2999. return facets.find((f) => Object.is(keyof(f.x), x) && Object.is(keyof(f.y), y));
  3000. }
  3001. function facetEmpty(facets, x, y) {
  3002. return facetFind(facets, x, y)?.empty;
  3003. }
  3004. function facetAnchorTop(facets, {y: Y}, {y}) {
  3005. return Y ? facetIndexOf(Y, y) === 0 : true;
  3006. }
  3007. function facetAnchorBottom(facets, {y: Y}, {y}) {
  3008. return Y ? facetIndexOf(Y, y) === Y.length - 1 : true;
  3009. }
  3010. function facetAnchorLeft(facets, {x: X}, {x}) {
  3011. return X ? facetIndexOf(X, x) === 0 : true;
  3012. }
  3013. function facetAnchorRight(facets, {x: X}, {x}) {
  3014. return X ? facetIndexOf(X, x) === X.length - 1 : true;
  3015. }
  3016. function facetAnchorTopEmpty(facets, {y: Y}, {x, y, empty}) {
  3017. if (empty) return false;
  3018. if (!Y) return;
  3019. const i = facetIndexOf(Y, y);
  3020. if (i > 0) return facetEmpty(facets, x, Y[i - 1]);
  3021. }
  3022. function facetAnchorBottomEmpty(facets, {y: Y}, {x, y, empty}) {
  3023. if (empty) return false;
  3024. if (!Y) return;
  3025. const i = facetIndexOf(Y, y);
  3026. if (i < Y.length - 1) return facetEmpty(facets, x, Y[i + 1]);
  3027. }
  3028. function facetAnchorLeftEmpty(facets, {x: X}, {x, y, empty}) {
  3029. if (empty) return false;
  3030. if (!X) return;
  3031. const i = facetIndexOf(X, x);
  3032. if (i > 0) return facetEmpty(facets, X[i - 1], y);
  3033. }
  3034. function facetAnchorRightEmpty(facets, {x: X}, {x, y, empty}) {
  3035. if (empty) return false;
  3036. if (!X) return;
  3037. const i = facetIndexOf(X, x);
  3038. if (i < X.length - 1) return facetEmpty(facets, X[i + 1], y);
  3039. }
  3040. function facetAnchorEmpty(facets, channels, {empty}) {
  3041. return empty;
  3042. }
  3043. function and(a, b) {
  3044. return function () {
  3045. return a.apply(null, arguments) && b.apply(null, arguments);
  3046. };
  3047. }
  3048. // Facet filter, by mark; for now only the "eq" filter is provided.
  3049. function facetFilter(facets, {channels: {fx, fy}, groups}) {
  3050. return fx && fy
  3051. ? facets.map(({x, y}) => groups.get(x)?.get(y) ?? [])
  3052. : fx
  3053. ? facets.map(({x}) => groups.get(x) ?? [])
  3054. : facets.map(({y}) => groups.get(y) ?? []);
  3055. }
  3056. const pi = Math.PI;
  3057. const tau = 2 * pi;
  3058. const defaultAspectRatio = 0.618;
  3059. function createProjection(
  3060. {
  3061. projection,
  3062. inset: globalInset = 0,
  3063. insetTop = globalInset,
  3064. insetRight = globalInset,
  3065. insetBottom = globalInset,
  3066. insetLeft = globalInset
  3067. } = {},
  3068. dimensions
  3069. ) {
  3070. if (projection == null) return;
  3071. if (typeof projection.stream === "function") return projection; // d3 projection
  3072. let options;
  3073. let domain;
  3074. let clip = "frame";
  3075. // If the projection was specified as an object with additional options,
  3076. // extract those. The order of precedence for insetTop (and other insets) is:
  3077. // projection.insetTop, projection.inset, (global) insetTop, (global) inset.
  3078. // Any other options on this object will be passed through to the initializer.
  3079. if (isObject(projection)) {
  3080. let inset;
  3081. ({
  3082. type: projection,
  3083. domain,
  3084. inset,
  3085. insetTop = inset !== undefined ? inset : insetTop,
  3086. insetRight = inset !== undefined ? inset : insetRight,
  3087. insetBottom = inset !== undefined ? inset : insetBottom,
  3088. insetLeft = inset !== undefined ? inset : insetLeft,
  3089. clip = clip,
  3090. ...options
  3091. } = projection);
  3092. if (projection == null) return;
  3093. }
  3094. // For named projections, retrieve the corresponding projection initializer.
  3095. if (typeof projection !== "function") ({type: projection} = namedProjection(projection));
  3096. // Compute the frame dimensions and invoke the projection initializer.
  3097. const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions;
  3098. const dx = width - marginLeft - marginRight - insetLeft - insetRight;
  3099. const dy = height - marginTop - marginBottom - insetTop - insetBottom;
  3100. projection = projection?.({width: dx, height: dy, clip, ...options});
  3101. // The projection initializer might decide to not use a projection.
  3102. if (projection == null) return;
  3103. clip = maybePostClip(clip, marginLeft, marginTop, width - marginRight, height - marginBottom);
  3104. // Translate the origin to the top-left corner, respecting margins and insets.
  3105. let tx = marginLeft + insetLeft;
  3106. let ty = marginTop + insetTop;
  3107. let transform;
  3108. // If a domain is specified, fit the projection to the frame.
  3109. if (domain != null) {
  3110. const [[x0, y0], [x1, y1]] = d3.geoPath(projection).bounds(domain);
  3111. const k = Math.min(dx / (x1 - x0), dy / (y1 - y0));
  3112. if (k > 0) {
  3113. tx -= (k * (x0 + x1) - dx) / 2;
  3114. ty -= (k * (y0 + y1) - dy) / 2;
  3115. transform = d3.geoTransform({
  3116. point(x, y) {
  3117. this.stream.point(x * k + tx, y * k + ty);
  3118. }
  3119. });
  3120. } else {
  3121. warn(`Warning: the projection could not be fit to the specified domain; using the default scale.`);
  3122. }
  3123. }
  3124. transform ??=
  3125. tx === 0 && ty === 0
  3126. ? identity()
  3127. : d3.geoTransform({
  3128. point(x, y) {
  3129. this.stream.point(x + tx, y + ty);
  3130. }
  3131. });
  3132. return {stream: (s) => projection.stream(transform.stream(clip(s)))};
  3133. }
  3134. function namedProjection(projection) {
  3135. switch (`${projection}`.toLowerCase()) {
  3136. case "albers-usa":
  3137. return scaleProjection(d3.geoAlbersUsa, 0.7463, 0.4673);
  3138. case "albers":
  3139. return conicProjection(d3.geoAlbers, 0.7463, 0.4673);
  3140. case "azimuthal-equal-area":
  3141. return scaleProjection(d3.geoAzimuthalEqualArea, 4, 4);
  3142. case "azimuthal-equidistant":
  3143. return scaleProjection(d3.geoAzimuthalEquidistant, tau, tau);
  3144. case "conic-conformal":
  3145. return conicProjection(d3.geoConicConformal, tau, tau);
  3146. case "conic-equal-area":
  3147. return conicProjection(d3.geoConicEqualArea, 6.1702, 2.9781);
  3148. case "conic-equidistant":
  3149. return conicProjection(d3.geoConicEquidistant, 7.312, 3.6282);
  3150. case "equal-earth":
  3151. return scaleProjection(d3.geoEqualEarth, 5.4133, 2.6347);
  3152. case "equirectangular":
  3153. return scaleProjection(d3.geoEquirectangular, tau, pi);
  3154. case "gnomonic":
  3155. return scaleProjection(d3.geoGnomonic, 3.4641, 3.4641);
  3156. case "identity":
  3157. return {type: identity};
  3158. case "reflect-y":
  3159. return {type: reflectY};
  3160. case "mercator":
  3161. return scaleProjection(d3.geoMercator, tau, tau);
  3162. case "orthographic":
  3163. return scaleProjection(d3.geoOrthographic, 2, 2);
  3164. case "stereographic":
  3165. return scaleProjection(d3.geoStereographic, 2, 2);
  3166. case "transverse-mercator":
  3167. return scaleProjection(d3.geoTransverseMercator, tau, tau);
  3168. default:
  3169. throw new Error(`unknown projection type: ${projection}`);
  3170. }
  3171. }
  3172. function maybePostClip(clip, x1, y1, x2, y2) {
  3173. if (clip === false || clip == null || typeof clip === "number") return (s) => s;
  3174. if (clip === true) clip = "frame";
  3175. switch (`${clip}`.toLowerCase()) {
  3176. case "frame":
  3177. return d3.geoClipRectangle(x1, y1, x2, y2);
  3178. default:
  3179. throw new Error(`unknown projection clip type: ${clip}`);
  3180. }
  3181. }
  3182. function scaleProjection(createProjection, kx, ky) {
  3183. return {
  3184. type: ({width, height, rotate, precision = 0.15, clip}) => {
  3185. const projection = createProjection();
  3186. if (precision != null) projection.precision?.(precision);
  3187. if (rotate != null) projection.rotate?.(rotate);
  3188. if (typeof clip === "number") projection.clipAngle?.(clip);
  3189. if (width != null) {
  3190. projection.scale(Math.min(width / kx, height / ky));
  3191. projection.translate([width / 2, height / 2]);
  3192. }
  3193. return projection;
  3194. },
  3195. aspectRatio: ky / kx
  3196. };
  3197. }
  3198. function conicProjection(createProjection, kx, ky) {
  3199. const {type, aspectRatio} = scaleProjection(createProjection, kx, ky);
  3200. return {
  3201. type: (options) => {
  3202. const {parallels, domain, width, height} = options;
  3203. const projection = type(options);
  3204. if (parallels != null) {
  3205. projection.parallels(parallels);
  3206. if (domain === undefined && width != null) {
  3207. projection.fitSize([width, height], {type: "Sphere"});
  3208. }
  3209. }
  3210. return projection;
  3211. },
  3212. aspectRatio
  3213. };
  3214. }
  3215. const identity = constant({stream: (stream) => stream});
  3216. const reflectY = constant(
  3217. d3.geoTransform({
  3218. point(x, y) {
  3219. this.stream.point(x, -y);
  3220. }
  3221. })
  3222. );
  3223. // Applies a point-wise projection to the given paired x and y channels.
  3224. // Note: mutates values!
  3225. function project(cx, cy, values, projection) {
  3226. const x = values[cx];
  3227. const y = values[cy];
  3228. const n = x.length;
  3229. const X = (values[cx] = new Float64Array(n).fill(NaN));
  3230. const Y = (values[cy] = new Float64Array(n).fill(NaN));
  3231. let i;
  3232. const stream = projection.stream({
  3233. point(x, y) {
  3234. X[i] = x;
  3235. Y[i] = y;
  3236. }
  3237. });
  3238. for (i = 0; i < n; ++i) {
  3239. stream.point(x[i], y[i]);
  3240. }
  3241. }
  3242. // Returns true if a projection was specified. This should match the logic of
  3243. // createProjection above, and is called before we construct the projection.
  3244. // (Though note that we ignore the edge case where the projection initializer
  3245. // may return null.)
  3246. function hasProjection({projection} = {}) {
  3247. if (projection == null) return false;
  3248. if (typeof projection.stream === "function") return true;
  3249. if (isObject(projection)) projection = projection.type;
  3250. return projection != null;
  3251. }
  3252. // When a projection is specified, we can use its aspect ratio to determine a
  3253. // good value for the projection’s height based on the desired width. When we
  3254. // don’t have a way to know, the golden ratio is our best guess. Due to a
  3255. // circular dependency (we need to know the height before we can construct the
  3256. // projection), we have to test the raw projection option rather than the
  3257. // materialized projection; therefore we must be extremely careful that the
  3258. // logic of this function exactly matches createProjection above!
  3259. function projectionAspectRatio(projection) {
  3260. if (typeof projection?.stream === "function") return defaultAspectRatio;
  3261. if (isObject(projection)) {
  3262. let domain, options;
  3263. ({domain, type: projection, ...options} = projection);
  3264. if (domain != null && projection != null) {
  3265. const type = typeof projection === "string" ? namedProjection(projection).type : projection;
  3266. const [[x0, y0], [x1, y1]] = d3.geoPath(type({...options, width: 100, height: 100})).bounds(domain);
  3267. const r = (y1 - y0) / (x1 - x0);
  3268. return r && isFinite(r) ? (r < 0.2 ? 0.2 : r > 5 ? 5 : r) : defaultAspectRatio;
  3269. }
  3270. }
  3271. if (projection == null) return;
  3272. if (typeof projection !== "function") {
  3273. const {aspectRatio} = namedProjection(projection);
  3274. if (aspectRatio) return aspectRatio;
  3275. }
  3276. return defaultAspectRatio;
  3277. }
  3278. // Extract the (possibly) scaled values for the x and y channels, and apply the
  3279. // projection if any.
  3280. function applyPosition(channels, scales, {projection}) {
  3281. const {x, y} = channels;
  3282. let position = {};
  3283. if (x) position.x = x;
  3284. if (y) position.y = y;
  3285. position = valueObject(position, scales);
  3286. if (projection && x?.scale === "x" && y?.scale === "y") project("x", "y", position, projection);
  3287. if (x) position.x = coerceNumbers(position.x);
  3288. if (y) position.y = coerceNumbers(position.y);
  3289. return position;
  3290. }
  3291. function getGeometryChannels(channel) {
  3292. const X = [];
  3293. const Y = [];
  3294. const x = {scale: "x", value: X};
  3295. const y = {scale: "y", value: Y};
  3296. const sink = {
  3297. point(x, y) {
  3298. X.push(x);
  3299. Y.push(y);
  3300. },
  3301. lineStart() {},
  3302. lineEnd() {},
  3303. polygonStart() {},
  3304. polygonEnd() {},
  3305. sphere() {}
  3306. };
  3307. for (const object of channel.value) d3.geoStream(object, sink);
  3308. return [x, y];
  3309. }
  3310. // If no projection is specified, default to a projection that passes points
  3311. // through the x and y scales, if any.
  3312. function xyProjection({x: X, y: Y}) {
  3313. if (X || Y) {
  3314. X ??= (x) => x;
  3315. Y ??= (y) => y;
  3316. return d3.geoTransform({
  3317. point(x, y) {
  3318. this.stream.point(X(x), Y(y));
  3319. }
  3320. });
  3321. }
  3322. }
  3323. function createContext(options = {}) {
  3324. const {document = typeof window !== "undefined" ? window.document : undefined, clip} = options;
  3325. return {document, clip: maybeClip(clip)};
  3326. }
  3327. function create(name, {document}) {
  3328. return d3.select(d3.creator(name).call(document.documentElement));
  3329. }
  3330. const unset = Symbol("unset");
  3331. function memoize1(compute) {
  3332. return (compute.length === 1 ? memoize1Arg : memoize1Args)(compute);
  3333. }
  3334. function memoize1Arg(compute) {
  3335. let cacheValue;
  3336. let cacheKey = unset;
  3337. return (key) => {
  3338. if (!Object.is(cacheKey, key)) {
  3339. cacheKey = key;
  3340. cacheValue = compute(key);
  3341. }
  3342. return cacheValue;
  3343. };
  3344. }
  3345. function memoize1Args(compute) {
  3346. let cacheValue, cacheKeys;
  3347. return (...keys) => {
  3348. if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => !Object.is(k, keys[i]))) {
  3349. cacheKeys = keys;
  3350. cacheValue = compute(...keys);
  3351. }
  3352. return cacheValue;
  3353. };
  3354. }
  3355. const numberFormat = memoize1((locale) => {
  3356. return new Intl.NumberFormat(locale);
  3357. });
  3358. const monthFormat = memoize1((locale, month) => {
  3359. return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(month && {month})});
  3360. });
  3361. const weekdayFormat = memoize1((locale, weekday) => {
  3362. return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(weekday && {weekday})});
  3363. });
  3364. function formatNumber(locale = "en-US") {
  3365. const format = numberFormat(locale);
  3366. return (i) => (i != null && !isNaN(i) ? format.format(i) : undefined);
  3367. }
  3368. function formatMonth(locale = "en-US", format = "short") {
  3369. const fmt = monthFormat(locale, format);
  3370. return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2000, +i)))) ? fmt.format(i) : undefined);
  3371. }
  3372. function formatWeekday(locale = "en-US", format = "short") {
  3373. const fmt = weekdayFormat(locale, format);
  3374. return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2001, 0, +i)))) ? fmt.format(i) : undefined);
  3375. }
  3376. function formatIsoDate(date) {
  3377. return format(date, "Invalid Date");
  3378. }
  3379. function formatAuto(locale = "en-US") {
  3380. const number = formatNumber(locale);
  3381. return (v) => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v);
  3382. }
  3383. // TODO When Plot supports a top-level locale option, this should be removed
  3384. // because it lacks context to know which locale to use; formatAuto should be
  3385. // used instead whenever possible.
  3386. const formatDefault = formatAuto();
  3387. const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore
  3388. let nextClipId = 0;
  3389. let nextPatternId = 0;
  3390. function getClipId() {
  3391. return `plot-clip-${++nextClipId}`;
  3392. }
  3393. function getPatternId() {
  3394. return `plot-pattern-${++nextPatternId}`;
  3395. }
  3396. function styles(
  3397. mark,
  3398. {
  3399. title,
  3400. href,
  3401. ariaLabel: variaLabel,
  3402. ariaDescription,
  3403. ariaHidden,
  3404. target,
  3405. fill,
  3406. fillOpacity,
  3407. stroke,
  3408. strokeWidth,
  3409. strokeOpacity,
  3410. strokeLinejoin,
  3411. strokeLinecap,
  3412. strokeMiterlimit,
  3413. strokeDasharray,
  3414. strokeDashoffset,
  3415. opacity,
  3416. mixBlendMode,
  3417. imageFilter,
  3418. paintOrder,
  3419. pointerEvents,
  3420. shapeRendering,
  3421. channels
  3422. },
  3423. {
  3424. ariaLabel: cariaLabel,
  3425. fill: defaultFill = "currentColor",
  3426. fillOpacity: defaultFillOpacity,
  3427. stroke: defaultStroke = "none",
  3428. strokeOpacity: defaultStrokeOpacity,
  3429. strokeWidth: defaultStrokeWidth,
  3430. strokeLinecap: defaultStrokeLinecap,
  3431. strokeLinejoin: defaultStrokeLinejoin,
  3432. strokeMiterlimit: defaultStrokeMiterlimit,
  3433. paintOrder: defaultPaintOrder
  3434. }
  3435. ) {
  3436. // Some marks don’t support fill (e.g., tick and rule).
  3437. if (defaultFill === null) {
  3438. fill = null;
  3439. fillOpacity = null;
  3440. }
  3441. // Some marks don’t support stroke (e.g., image).
  3442. if (defaultStroke === null) {
  3443. stroke = null;
  3444. strokeOpacity = null;
  3445. }
  3446. // Some marks default to fill with no stroke, while others default to stroke
  3447. // with no fill. For example, bar and area default to fill, while dot and line
  3448. // default to stroke. For marks that fill by default, the default fill only
  3449. // applies if the stroke is (constant) none; if you set a stroke, then the
  3450. // default fill becomes none. Similarly for marks that stroke by stroke, the
  3451. // default stroke only applies if the fill is (constant) none.
  3452. if (isNoneish(defaultFill)) {
  3453. if (!isNoneish(defaultStroke) && (!isNoneish(fill) || channels?.fill)) defaultStroke = "none";
  3454. } else {
  3455. if (isNoneish(defaultStroke) && (!isNoneish(stroke) || channels?.stroke)) defaultFill = "none";
  3456. }
  3457. const [vfill, cfill] = maybeColorChannel(fill, defaultFill);
  3458. const [vfillOpacity, cfillOpacity] = maybeNumberChannel(fillOpacity, defaultFillOpacity);
  3459. const [vstroke, cstroke] = maybeColorChannel(stroke, defaultStroke);
  3460. const [vstrokeOpacity, cstrokeOpacity] = maybeNumberChannel(strokeOpacity, defaultStrokeOpacity);
  3461. const [vopacity, copacity] = maybeNumberChannel(opacity);
  3462. // For styles that have no effect if there is no stroke, only apply the
  3463. // defaults if the stroke is not the constant none. (If stroke is a channel,
  3464. // then cstroke will be undefined, but there’s still a stroke; hence we don’t
  3465. // use isNoneish here.)
  3466. if (!isNone(cstroke)) {
  3467. if (strokeWidth === undefined) strokeWidth = defaultStrokeWidth;
  3468. if (strokeLinecap === undefined) strokeLinecap = defaultStrokeLinecap;
  3469. if (strokeLinejoin === undefined) strokeLinejoin = defaultStrokeLinejoin;
  3470. // The default stroke miterlimit need not be applied if the current stroke
  3471. // is the constant round; this only has effect on miter joins.
  3472. if (strokeMiterlimit === undefined && !isRound(strokeLinejoin)) strokeMiterlimit = defaultStrokeMiterlimit;
  3473. // The paint order only takes effect if there is both a fill and a stroke
  3474. // (at least if we ignore markers, which no built-in marks currently use).
  3475. if (!isNone(cfill) && paintOrder === undefined) paintOrder = defaultPaintOrder;
  3476. }
  3477. const [vstrokeWidth, cstrokeWidth] = maybeNumberChannel(strokeWidth);
  3478. // Some marks don’t support fill (e.g., tick and rule).
  3479. if (defaultFill !== null) {
  3480. mark.fill = impliedString(cfill, "currentColor");
  3481. mark.fillOpacity = impliedNumber(cfillOpacity, 1);
  3482. }
  3483. // Some marks don’t support stroke (e.g., image).
  3484. if (defaultStroke !== null) {
  3485. mark.stroke = impliedString(cstroke, "none");
  3486. mark.strokeWidth = impliedNumber(cstrokeWidth, 1);
  3487. mark.strokeOpacity = impliedNumber(cstrokeOpacity, 1);
  3488. mark.strokeLinejoin = impliedString(strokeLinejoin, "miter");
  3489. mark.strokeLinecap = impliedString(strokeLinecap, "butt");
  3490. mark.strokeMiterlimit = impliedNumber(strokeMiterlimit, 4);
  3491. mark.strokeDasharray = impliedString(strokeDasharray, "none");
  3492. mark.strokeDashoffset = impliedString(strokeDashoffset, "0");
  3493. }
  3494. mark.target = string(target);
  3495. mark.ariaLabel = string(cariaLabel);
  3496. mark.ariaDescription = string(ariaDescription);
  3497. mark.ariaHidden = string(ariaHidden);
  3498. mark.opacity = impliedNumber(copacity, 1);
  3499. mark.mixBlendMode = impliedString(mixBlendMode, "normal");
  3500. mark.imageFilter = impliedString(imageFilter, "none");
  3501. mark.paintOrder = impliedString(paintOrder, "normal");
  3502. mark.pointerEvents = impliedString(pointerEvents, "auto");
  3503. mark.shapeRendering = impliedString(shapeRendering, "auto");
  3504. return {
  3505. title: {value: title, optional: true, filter: null},
  3506. href: {value: href, optional: true, filter: null},
  3507. ariaLabel: {value: variaLabel, optional: true, filter: null},
  3508. fill: {value: vfill, scale: "auto", optional: true},
  3509. fillOpacity: {value: vfillOpacity, scale: "auto", optional: true},
  3510. stroke: {value: vstroke, scale: "auto", optional: true},
  3511. strokeOpacity: {value: vstrokeOpacity, scale: "auto", optional: true},
  3512. strokeWidth: {value: vstrokeWidth, optional: true},
  3513. opacity: {value: vopacity, scale: "auto", optional: true}
  3514. };
  3515. }
  3516. // Applies the specified titles via selection.call.
  3517. function applyTitle(selection, L) {
  3518. if (L)
  3519. selection
  3520. .filter((i) => nonempty(L[i]))
  3521. .append("title")
  3522. .call(applyText, L);
  3523. }
  3524. // Like applyTitle, but for grouped data (lines, areas).
  3525. function applyTitleGroup(selection, L) {
  3526. if (L)
  3527. selection
  3528. .filter(([i]) => nonempty(L[i]))
  3529. .append("title")
  3530. .call(applyTextGroup, L);
  3531. }
  3532. function applyText(selection, T) {
  3533. if (T) selection.text((i) => formatDefault(T[i]));
  3534. }
  3535. function applyTextGroup(selection, T) {
  3536. if (T) selection.text(([i]) => formatDefault(T[i]));
  3537. }
  3538. function applyChannelStyles(
  3539. selection,
  3540. {target, tip},
  3541. {
  3542. ariaLabel: AL,
  3543. title: T,
  3544. fill: F,
  3545. fillOpacity: FO,
  3546. stroke: S,
  3547. strokeOpacity: SO,
  3548. strokeWidth: SW,
  3549. opacity: O,
  3550. href: H
  3551. }
  3552. ) {
  3553. if (AL) applyAttr(selection, "aria-label", (i) => AL[i]);
  3554. if (F) applyAttr(selection, "fill", (i) => F[i]);
  3555. if (FO) applyAttr(selection, "fill-opacity", (i) => FO[i]);
  3556. if (S) applyAttr(selection, "stroke", (i) => S[i]);
  3557. if (SO) applyAttr(selection, "stroke-opacity", (i) => SO[i]);
  3558. if (SW) applyAttr(selection, "stroke-width", (i) => SW[i]);
  3559. if (O) applyAttr(selection, "opacity", (i) => O[i]);
  3560. if (H) applyHref(selection, (i) => H[i], target);
  3561. if (!tip) applyTitle(selection, T);
  3562. }
  3563. function applyGroupedChannelStyles(
  3564. selection,
  3565. {target, tip},
  3566. {
  3567. ariaLabel: AL,
  3568. title: T,
  3569. fill: F,
  3570. fillOpacity: FO,
  3571. stroke: S,
  3572. strokeOpacity: SO,
  3573. strokeWidth: SW,
  3574. opacity: O,
  3575. href: H
  3576. }
  3577. ) {
  3578. if (AL) applyAttr(selection, "aria-label", ([i]) => AL[i]);
  3579. if (F) applyAttr(selection, "fill", ([i]) => F[i]);
  3580. if (FO) applyAttr(selection, "fill-opacity", ([i]) => FO[i]);
  3581. if (S) applyAttr(selection, "stroke", ([i]) => S[i]);
  3582. if (SO) applyAttr(selection, "stroke-opacity", ([i]) => SO[i]);
  3583. if (SW) applyAttr(selection, "stroke-width", ([i]) => SW[i]);
  3584. if (O) applyAttr(selection, "opacity", ([i]) => O[i]);
  3585. if (H) applyHref(selection, ([i]) => H[i], target);
  3586. if (!tip) applyTitleGroup(selection, T);
  3587. }
  3588. function groupAesthetics(
  3589. {
  3590. ariaLabel: AL,
  3591. title: T,
  3592. fill: F,
  3593. fillOpacity: FO,
  3594. stroke: S,
  3595. strokeOpacity: SO,
  3596. strokeWidth: SW,
  3597. opacity: O,
  3598. href: H
  3599. },
  3600. {tip}
  3601. ) {
  3602. return [AL, tip ? undefined : T, F, FO, S, SO, SW, O, H].filter((c) => c !== undefined);
  3603. }
  3604. function groupZ(I, Z, z) {
  3605. const G = d3.group(I, (i) => Z[i]);
  3606. if (z === undefined && G.size > (1 + I.length) >> 1) {
  3607. warn(
  3608. `Warning: the implicit z channel has high cardinality. This may occur when the fill or stroke channel is associated with quantitative data rather than ordinal or categorical data. You can suppress this warning by setting the z option explicitly; if this data represents a single series, set z to null.`
  3609. );
  3610. }
  3611. return G.values();
  3612. }
  3613. function* groupIndex(I, position, mark, channels) {
  3614. const {z} = mark;
  3615. const {z: Z} = channels; // group channel
  3616. const A = groupAesthetics(channels, mark); // aesthetic channels
  3617. const C = [...position, ...A]; // all channels
  3618. // Group the current index by Z (if any).
  3619. for (const G of Z ? groupZ(I, Z, z) : [I]) {
  3620. let Ag; // the A-values (aesthetics) of the current group, if any
  3621. let Gg; // the current group index (a subset of G, and I), if any
  3622. out: for (const i of G) {
  3623. // If any channel has an undefined value for this index, skip it.
  3624. for (const c of C) {
  3625. if (!defined(c[i])) {
  3626. if (Gg) Gg.push(-1);
  3627. continue out;
  3628. }
  3629. }
  3630. // Otherwise, if this is a new group, record the aesthetics for this
  3631. // group. Yield the current group and start a new one.
  3632. if (Ag === undefined) {
  3633. if (Gg) yield Gg;
  3634. (Ag = A.map((c) => keyof(c[i]))), (Gg = [i]);
  3635. continue;
  3636. }
  3637. // Otherwise, add the current index to the current group. Then, if any of
  3638. // the aesthetics don’t match the current group, yield the current group
  3639. // and start a new group of the current index.
  3640. Gg.push(i);
  3641. for (let j = 0; j < A.length; ++j) {
  3642. const k = keyof(A[j][i]);
  3643. if (k !== Ag[j]) {
  3644. yield Gg;
  3645. (Ag = A.map((c) => keyof(c[i]))), (Gg = [i]);
  3646. continue out;
  3647. }
  3648. }
  3649. }
  3650. // Yield the current group, if any.
  3651. if (Gg) yield Gg;
  3652. }
  3653. }
  3654. // Note: may mutate selection.node!
  3655. function applyClip(selection, mark, dimensions, context) {
  3656. let clipUrl;
  3657. const {clip = context.clip} = mark;
  3658. if (clip === "frame") {
  3659. // Wrap the G element with another (untransformed) G element, applying the
  3660. // clip to the parent G element so that the clip path is not affected by
  3661. // the mark’s transform. To simplify the adoption of this fix, mutate the
  3662. // passed-in selection.node to return the parent G element.
  3663. selection = create("svg:g", context).each(function () {
  3664. this.appendChild(selection.node());
  3665. selection.node = () => this; // Note: mutation!
  3666. });
  3667. clipUrl = getFrameClip(context, dimensions);
  3668. } else if (clip) {
  3669. clipUrl = getGeoClip(clip, context);
  3670. }
  3671. // Here we’re careful to apply the ARIA attributes to the outer G element when
  3672. // clipping is applied, and to apply the ARIA attributes before any other
  3673. // attributes (for readability).
  3674. applyAttr(selection, "aria-label", mark.ariaLabel);
  3675. applyAttr(selection, "aria-description", mark.ariaDescription);
  3676. applyAttr(selection, "aria-hidden", mark.ariaHidden);
  3677. applyAttr(selection, "clip-path", clipUrl);
  3678. }
  3679. function memoizeClip(clip) {
  3680. const cache = new WeakMap();
  3681. return (context, dimensions) => {
  3682. let url = cache.get(context);
  3683. if (!url) {
  3684. const id = getClipId();
  3685. d3.select(context.ownerSVGElement).append("clipPath").attr("id", id).call(clip, context, dimensions);
  3686. cache.set(context, (url = `url(#${id})`));
  3687. }
  3688. return url;
  3689. };
  3690. }
  3691. const getFrameClip = memoizeClip((clipPath, context, dimensions) => {
  3692. const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions;
  3693. clipPath
  3694. .append("rect")
  3695. .attr("x", marginLeft)
  3696. .attr("y", marginTop)
  3697. .attr("width", width - marginRight - marginLeft)
  3698. .attr("height", height - marginTop - marginBottom);
  3699. });
  3700. const geoClipCache = new WeakMap();
  3701. const sphere$1 = {type: "Sphere"};
  3702. function getGeoClip(geo, context) {
  3703. let cache, url;
  3704. if (!(cache = geoClipCache.get(context))) geoClipCache.set(context, (cache = new WeakMap()));
  3705. if (geo.type === "Sphere") geo = sphere$1; // coalesce all spheres
  3706. if (!(url = cache.get(geo))) {
  3707. const id = getClipId();
  3708. d3.select(context.ownerSVGElement).append("clipPath").attr("id", id).append("path").attr("d", context.path()(geo));
  3709. cache.set(geo, (url = `url(#${id})`));
  3710. }
  3711. return url;
  3712. }
  3713. // Note: may mutate selection.node!
  3714. function applyIndirectStyles(selection, mark, dimensions, context) {
  3715. applyClip(selection, mark, dimensions, context);
  3716. applyAttr(selection, "class", mark.className);
  3717. applyAttr(selection, "fill", mark.fill);
  3718. applyAttr(selection, "fill-opacity", mark.fillOpacity);
  3719. applyAttr(selection, "stroke", mark.stroke);
  3720. applyAttr(selection, "stroke-width", mark.strokeWidth);
  3721. applyAttr(selection, "stroke-opacity", mark.strokeOpacity);
  3722. applyAttr(selection, "stroke-linejoin", mark.strokeLinejoin);
  3723. applyAttr(selection, "stroke-linecap", mark.strokeLinecap);
  3724. applyAttr(selection, "stroke-miterlimit", mark.strokeMiterlimit);
  3725. applyAttr(selection, "stroke-dasharray", mark.strokeDasharray);
  3726. applyAttr(selection, "stroke-dashoffset", mark.strokeDashoffset);
  3727. applyAttr(selection, "shape-rendering", mark.shapeRendering);
  3728. applyAttr(selection, "filter", mark.imageFilter);
  3729. applyAttr(selection, "paint-order", mark.paintOrder);
  3730. const {pointerEvents = context.pointerSticky === false ? "none" : undefined} = mark;
  3731. applyAttr(selection, "pointer-events", pointerEvents);
  3732. }
  3733. function applyDirectStyles(selection, mark) {
  3734. applyStyle(selection, "mix-blend-mode", mark.mixBlendMode);
  3735. applyAttr(selection, "opacity", mark.opacity);
  3736. }
  3737. function applyHref(selection, href, target) {
  3738. selection.each(function (i) {
  3739. const h = href(i);
  3740. if (h != null) {
  3741. const a = this.ownerDocument.createElementNS(d3.namespaces.svg, "a");
  3742. a.setAttribute("fill", "inherit");
  3743. a.setAttributeNS(d3.namespaces.xlink, "href", h);
  3744. if (target != null) a.setAttribute("target", target);
  3745. this.parentNode.insertBefore(a, this).appendChild(this);
  3746. }
  3747. });
  3748. }
  3749. function applyAttr(selection, name, value) {
  3750. if (value != null) selection.attr(name, value);
  3751. }
  3752. function applyStyle(selection, name, value) {
  3753. if (value != null) selection.style(name, value);
  3754. }
  3755. function applyTransform(selection, mark, {x, y}, tx = offset, ty = offset) {
  3756. tx += mark.dx;
  3757. ty += mark.dy;
  3758. if (x?.bandwidth) tx += x.bandwidth() / 2;
  3759. if (y?.bandwidth) ty += y.bandwidth() / 2;
  3760. if (tx || ty) selection.attr("transform", `translate(${tx},${ty})`);
  3761. }
  3762. function impliedString(value, impliedValue) {
  3763. if ((value = string(value)) !== impliedValue) return value;
  3764. }
  3765. function impliedNumber(value, impliedValue) {
  3766. if ((value = number$1(value)) !== impliedValue) return value;
  3767. }
  3768. // https://www.w3.org/TR/CSS21/grammar.html
  3769. const validClassName =
  3770. /^-?([_a-z]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])([_a-z0-9-]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])*$/i;
  3771. function maybeClassName(name) {
  3772. // The default should be changed whenever the default styles are changed, so
  3773. // as to avoid conflict when multiple versions of Plot are on the page.
  3774. if (name === undefined) return "plot-d6a7b5";
  3775. name = `${name}`;
  3776. if (!validClassName.test(name)) throw new Error(`invalid class name: ${name}`);
  3777. return name;
  3778. }
  3779. function applyInlineStyles(selection, style) {
  3780. if (typeof style === "string") {
  3781. selection.property("style", style);
  3782. } else if (style != null) {
  3783. for (const element of selection) {
  3784. Object.assign(element.style, style);
  3785. }
  3786. }
  3787. }
  3788. function applyFrameAnchor({frameAnchor}, {width, height, marginTop, marginRight, marginBottom, marginLeft}) {
  3789. return [
  3790. /left$/.test(frameAnchor)
  3791. ? marginLeft
  3792. : /right$/.test(frameAnchor)
  3793. ? width - marginRight
  3794. : (marginLeft + width - marginRight) / 2,
  3795. /^top/.test(frameAnchor)
  3796. ? marginTop
  3797. : /^bottom/.test(frameAnchor)
  3798. ? height - marginBottom
  3799. : (marginTop + height - marginBottom) / 2
  3800. ];
  3801. }
  3802. class Mark {
  3803. constructor(data, channels = {}, options = {}, defaults) {
  3804. const {
  3805. facet = "auto",
  3806. facetAnchor,
  3807. fx,
  3808. fy,
  3809. sort,
  3810. dx = 0,
  3811. dy = 0,
  3812. margin = 0,
  3813. marginTop = margin,
  3814. marginRight = margin,
  3815. marginBottom = margin,
  3816. marginLeft = margin,
  3817. className,
  3818. clip = defaults?.clip,
  3819. channels: extraChannels,
  3820. tip,
  3821. render
  3822. } = options;
  3823. this.data = data;
  3824. this.sort = isDomainSort(sort) ? sort : null;
  3825. this.initializer = initializer(options).initializer;
  3826. this.transform = this.initializer ? options.transform : basic(options).transform;
  3827. if (facet === null || facet === false) {
  3828. this.facet = null;
  3829. } else {
  3830. this.facet = keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude", "super"]);
  3831. this.fx = data === singleton && typeof fx === "string" ? [fx] : fx;
  3832. this.fy = data === singleton && typeof fy === "string" ? [fy] : fy;
  3833. }
  3834. this.facetAnchor = maybeFacetAnchor(facetAnchor);
  3835. channels = maybeNamed(channels);
  3836. if (extraChannels !== undefined) channels = {...maybeChannels(extraChannels), ...channels};
  3837. if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels};
  3838. this.channels = Object.fromEntries(
  3839. Object.entries(channels)
  3840. .map(([name, channel]) => {
  3841. if (isOptions(channel.value)) {
  3842. // apply scale and label overrides
  3843. const {value, label = channel.label, scale = channel.scale} = channel.value;
  3844. channel = {...channel, label, scale, value};
  3845. }
  3846. if (data === singleton && typeof channel.value === "string") {
  3847. // convert field names to singleton values for decoration marks (e.g., frame)
  3848. const {value} = channel;
  3849. channel = {...channel, value: [value]};
  3850. }
  3851. return [name, channel];
  3852. })
  3853. .filter(([name, {value, optional}]) => {
  3854. if (value != null) return true;
  3855. if (optional) return false;
  3856. throw new Error(`missing channel value: ${name}`);
  3857. })
  3858. );
  3859. this.dx = +dx;
  3860. this.dy = +dy;
  3861. this.marginTop = +marginTop;
  3862. this.marginRight = +marginRight;
  3863. this.marginBottom = +marginBottom;
  3864. this.marginLeft = +marginLeft;
  3865. this.clip = maybeClip(clip);
  3866. this.tip = maybeTip(tip);
  3867. this.className = string(className);
  3868. // Super-faceting currently disallow position channels; in the future, we
  3869. // could allow position to be specified in fx and fy in addition to (or
  3870. // instead of) x and y.
  3871. if (this.facet === "super") {
  3872. if (fx || fy) throw new Error(`super-faceting cannot use fx or fy`);
  3873. for (const name in this.channels) {
  3874. const {scale} = channels[name];
  3875. if (scale !== "x" && scale !== "y") continue;
  3876. throw new Error(`super-faceting cannot use x or y`);
  3877. }
  3878. }
  3879. if (render != null) {
  3880. this.render = composeRender(render, this.render);
  3881. }
  3882. }
  3883. initialize(facets, facetChannels, plotOptions) {
  3884. let data = dataify(this.data);
  3885. if (facets === undefined && data != null) facets = [range(data)];
  3886. const originalFacets = facets;
  3887. if (this.transform != null) (({facets, data} = this.transform(data, facets, plotOptions))), (data = dataify(data));
  3888. if (facets !== undefined) facets.original = originalFacets; // needed to read facetChannels
  3889. const channels = createChannels(this.channels, data);
  3890. if (this.sort != null) channelDomain(data, facets, channels, facetChannels, this.sort); // mutates facetChannels!
  3891. return {data, facets, channels};
  3892. }
  3893. filter(index, channels, values) {
  3894. for (const name in channels) {
  3895. const {filter = defined} = channels[name];
  3896. if (filter !== null) {
  3897. const value = values[name];
  3898. index = index.filter((i) => filter(value[i]));
  3899. }
  3900. }
  3901. return index;
  3902. }
  3903. // If there is a projection, and there are paired x and y channels associated
  3904. // with the x and y scale respectively (and not already in screen coordinates
  3905. // as with an initializer), then apply the projection, replacing the x and y
  3906. // values. Note that the x and y scales themselves don’t exist if there is a
  3907. // projection, but whether the channels are associated with scales still
  3908. // determines whether the projection should apply; think of the projection as
  3909. // a combination xy-scale.
  3910. project(channels, values, context) {
  3911. for (const cx in channels) {
  3912. if (channels[cx].scale === "x" && /^x|x$/.test(cx)) {
  3913. const cy = cx.replace(/^x|x$/, "y");
  3914. if (cy in channels && channels[cy].scale === "y") {
  3915. project(cx, cy, values, context.projection);
  3916. }
  3917. }
  3918. }
  3919. }
  3920. scale(channels, scales, context) {
  3921. const values = valueObject(channels, scales);
  3922. if (context.projection) this.project(channels, values, context);
  3923. return values;
  3924. }
  3925. }
  3926. function marks(...marks) {
  3927. marks.plot = Mark.prototype.plot;
  3928. return marks;
  3929. }
  3930. function composeRender(r1, r2) {
  3931. if (r1 == null) return r2 === null ? undefined : r2;
  3932. if (r2 == null) return r1 === null ? undefined : r1;
  3933. if (typeof r1 !== "function") throw new TypeError(`invalid render transform: ${r1}`);
  3934. if (typeof r2 !== "function") throw new TypeError(`invalid render transform: ${r2}`);
  3935. return function (i, s, v, d, c, next) {
  3936. return r1.call(this, i, s, v, d, c, (i, s, v, d, c) => {
  3937. return r2.call(this, i, s, v, d, c, next); // preserve this
  3938. });
  3939. };
  3940. }
  3941. function maybeChannels(channels) {
  3942. return Object.fromEntries(
  3943. Object.entries(maybeNamed(channels)).map(([name, channel]) => {
  3944. channel = typeof channel === "string" ? {value: channel, label: name} : maybeValue(channel); // for shorthand extra channels, use name as label
  3945. if (channel.filter === undefined && channel.scale == null) channel = {...channel, filter: null};
  3946. return [name, channel];
  3947. })
  3948. );
  3949. }
  3950. function maybeTip(tip) {
  3951. return tip === true
  3952. ? "xy"
  3953. : tip === false || tip == null
  3954. ? null
  3955. : typeof tip === "string"
  3956. ? keyword(tip, "tip", ["x", "y", "xy"])
  3957. : tip; // tip options object
  3958. }
  3959. function withTip(options, pointer) {
  3960. return options?.tip === true
  3961. ? {...options, tip: pointer}
  3962. : isObject(options?.tip) && options.tip.pointer === undefined
  3963. ? {...options, tip: {...options.tip, pointer}}
  3964. : options;
  3965. }
  3966. function createDimensions(scales, marks, options = {}) {
  3967. // Compute the default margins: the maximum of the marks’ margins. While not
  3968. // always used, they may be needed to compute the default height of the plot.
  3969. let marginTopDefault = 0.5 - offset,
  3970. marginRightDefault = 0.5 + offset,
  3971. marginBottomDefault = 0.5 + offset,
  3972. marginLeftDefault = 0.5 - offset;
  3973. for (const {marginTop, marginRight, marginBottom, marginLeft} of marks) {
  3974. if (marginTop > marginTopDefault) marginTopDefault = marginTop;
  3975. if (marginRight > marginRightDefault) marginRightDefault = marginRight;
  3976. if (marginBottom > marginBottomDefault) marginBottomDefault = marginBottom;
  3977. if (marginLeft > marginLeftDefault) marginLeftDefault = marginLeft;
  3978. }
  3979. // Compute the actual margins. The order of precedence is: the side-specific
  3980. // margin options, then the global margin option, then the defaults.
  3981. let {
  3982. margin,
  3983. marginTop = margin !== undefined ? margin : marginTopDefault,
  3984. marginRight = margin !== undefined ? margin : marginRightDefault,
  3985. marginBottom = margin !== undefined ? margin : marginBottomDefault,
  3986. marginLeft = margin !== undefined ? margin : marginLeftDefault
  3987. } = options;
  3988. // Coerce the margin options to numbers.
  3989. marginTop = +marginTop;
  3990. marginRight = +marginRight;
  3991. marginBottom = +marginBottom;
  3992. marginLeft = +marginLeft;
  3993. // Compute the outer dimensions of the plot. If the top and bottom margins are
  3994. // specified explicitly, adjust the automatic height accordingly.
  3995. let {
  3996. width = 640,
  3997. height = autoHeight(scales, options, {
  3998. width,
  3999. marginTopDefault,
  4000. marginRightDefault,
  4001. marginBottomDefault,
  4002. marginLeftDefault
  4003. }) + Math.max(0, marginTop - marginTopDefault + marginBottom - marginBottomDefault)
  4004. } = options;
  4005. // Coerce the width and height.
  4006. width = +width;
  4007. height = +height;
  4008. const dimensions = {
  4009. width,
  4010. height,
  4011. marginTop,
  4012. marginRight,
  4013. marginBottom,
  4014. marginLeft
  4015. };
  4016. // Compute the facet margins.
  4017. if (scales.fx || scales.fy) {
  4018. let {
  4019. margin: facetMargin,
  4020. marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : marginTop,
  4021. marginRight: facetMarginRight = facetMargin !== undefined ? facetMargin : marginRight,
  4022. marginBottom: facetMarginBottom = facetMargin !== undefined ? facetMargin : marginBottom,
  4023. marginLeft: facetMarginLeft = facetMargin !== undefined ? facetMargin : marginLeft
  4024. } = options.facet ?? {};
  4025. // Coerce the facet margin options to numbers.
  4026. facetMarginTop = +facetMarginTop;
  4027. facetMarginRight = +facetMarginRight;
  4028. facetMarginBottom = +facetMarginBottom;
  4029. facetMarginLeft = +facetMarginLeft;
  4030. dimensions.facet = {
  4031. marginTop: facetMarginTop,
  4032. marginRight: facetMarginRight,
  4033. marginBottom: facetMarginBottom,
  4034. marginLeft: facetMarginLeft
  4035. };
  4036. }
  4037. return dimensions;
  4038. }
  4039. function autoHeight(
  4040. {x, y, fy, fx},
  4041. {projection, aspectRatio},
  4042. {width, marginTopDefault, marginRightDefault, marginBottomDefault, marginLeftDefault}
  4043. ) {
  4044. const nfy = fy ? fy.scale.domain().length || 1 : 1;
  4045. // If a projection is specified, compute an aspect ratio based on the domain,
  4046. // defaulting to the projection’s natural aspect ratio (if known).
  4047. const ar = projectionAspectRatio(projection);
  4048. if (ar) {
  4049. const nfx = fx ? fx.scale.domain().length : 1;
  4050. const far = ((1.1 * nfy - 0.1) / (1.1 * nfx - 0.1)) * ar; // 0.1 is default facet padding
  4051. const lar = Math.max(0.1, Math.min(10, far)); // clamp the aspect ratio to a “reasonable” value
  4052. return Math.round((width - marginLeftDefault - marginRightDefault) * lar + marginTopDefault + marginBottomDefault);
  4053. }
  4054. const ny = y ? (isOrdinalScale(y) ? y.scale.domain().length || 1 : Math.max(7, 17 / nfy)) : 1;
  4055. // If a desired aspect ratio is given, compute a default height to match.
  4056. if (aspectRatio != null) {
  4057. aspectRatio = +aspectRatio;
  4058. if (!(isFinite(aspectRatio) && aspectRatio > 0)) throw new Error(`invalid aspectRatio: ${aspectRatio}`);
  4059. const ratio = aspectRatioLength("y", y) / (aspectRatioLength("x", x) * aspectRatio);
  4060. const fxb = fx ? fx.scale.bandwidth() : 1;
  4061. const fyb = fy ? fy.scale.bandwidth() : 1;
  4062. const w = fxb * (width - marginLeftDefault - marginRightDefault) - x.insetLeft - x.insetRight;
  4063. return (ratio * w + y.insetTop + y.insetBottom) / fyb + marginTopDefault + marginBottomDefault;
  4064. }
  4065. return !!(y || fy) * Math.max(1, Math.min(60, ny * nfy)) * 20 + !!fx * 30 + 60;
  4066. }
  4067. function aspectRatioLength(k, scale) {
  4068. if (!scale) throw new Error(`aspectRatio requires ${k} scale`);
  4069. const {type, domain} = scale;
  4070. let transform;
  4071. switch (type) {
  4072. case "linear":
  4073. case "utc":
  4074. case "time":
  4075. transform = Number;
  4076. break;
  4077. case "pow": {
  4078. const exponent = scale.scale.exponent();
  4079. transform = (x) => Math.pow(x, exponent);
  4080. break;
  4081. }
  4082. case "log":
  4083. transform = Math.log;
  4084. break;
  4085. case "point":
  4086. case "band":
  4087. return domain.length;
  4088. default:
  4089. throw new Error(`unsupported ${k} scale for aspectRatio: ${type}`);
  4090. }
  4091. const [min, max] = d3.extent(domain);
  4092. return Math.abs(transform(max) - transform(min));
  4093. }
  4094. const states = new WeakMap();
  4095. function pointerK(kx, ky, {x, y, px, py, maxRadius = 40, channels, render, ...options} = {}) {
  4096. maxRadius = +maxRadius;
  4097. // When px or py is used, register an extra channel that the pointer
  4098. // interaction can use to control which point is focused; this allows pointing
  4099. // to function independently of where the downstream mark (e.g., a tip) is
  4100. // displayed. Also default x or y to null to disable maybeTuple etc.
  4101. if (px != null) (x ??= null), (channels = {...channels, px: {value: px, scale: "x"}});
  4102. if (py != null) (y ??= null), (channels = {...channels, py: {value: py, scale: "y"}});
  4103. return {
  4104. x,
  4105. y,
  4106. channels,
  4107. ...options,
  4108. // Unlike other composed transforms, the render transform must be the
  4109. // outermost render function because it will re-render dynamically in
  4110. // response to pointer events.
  4111. render: composeRender(function (index, scales, values, dimensions, context, next) {
  4112. context = {...context, pointerSticky: false};
  4113. const svg = context.ownerSVGElement;
  4114. const {data} = context.getMarkState(this);
  4115. // Isolate state per-pointer, per-plot; if the pointer is reused by
  4116. // multiple marks, they will share the same state (e.g., sticky modality).
  4117. let state = states.get(svg);
  4118. if (!state) states.set(svg, (state = {sticky: false, roots: [], renders: []}));
  4119. // This serves as a unique identifier of the rendered mark per-plot; it is
  4120. // used to record the currently-rendered elements (state.roots) so that we
  4121. // can tell when a rendered element is clicked on.
  4122. let renderIndex = state.renders.push(render) - 1;
  4123. // For faceting, we want to compute the local coordinates of each point,
  4124. // which means subtracting out the facet translation, if any. (It’s
  4125. // tempting to do this using the local coordinates in SVG, but that’s
  4126. // complicated by mark-specific transforms such as dx and dy.) Also, since
  4127. // band scales return the upper bound of the band, we have to offset by
  4128. // half the bandwidth.
  4129. const {x, y, fx, fy} = scales;
  4130. let tx = fx ? fx(index.fx) - dimensions.marginLeft : 0;
  4131. let ty = fy ? fy(index.fy) - dimensions.marginTop : 0;
  4132. if (x?.bandwidth) tx += x.bandwidth() / 2;
  4133. if (y?.bandwidth) ty += y.bandwidth() / 2;
  4134. // For faceting, we also need to record the closest point per facet per
  4135. // mark (!), since each facet has its own pointer event listeners; we only
  4136. // want the closest point across facets to be visible.
  4137. const faceted = index.fi != null;
  4138. let facetState;
  4139. if (faceted) {
  4140. let facetStates = state.facetStates;
  4141. if (!facetStates) state.facetStates = facetStates = new Map();
  4142. facetState = facetStates.get(this);
  4143. if (!facetState) facetStates.set(this, (facetState = new Map()));
  4144. }
  4145. // The order of precedence for the pointer position is: px & py; the
  4146. // middle of x1 & y1 and x2 & y2; or x1 & y1 (e.g., area); or lastly x &
  4147. // y. If a dimension is unspecified, the frame anchor is used.
  4148. const [cx, cy] = applyFrameAnchor(this, dimensions);
  4149. const {px: PX, py: PY} = values;
  4150. const px = PX ? (i) => PX[i] : anchorX$1(values, cx);
  4151. const py = PY ? (i) => PY[i] : anchorY$1(values, cy);
  4152. let i; // currently focused index
  4153. let g; // currently rendered mark
  4154. let s; // currently rendered stickiness
  4155. let f; // current animation frame
  4156. // When faceting, if more than one pointer would be visible, only show
  4157. // this one if it is the closest. We defer rendering using an animation
  4158. // frame to allow all pointer events to be received before deciding which
  4159. // mark to render; although when hiding, we render immediately.
  4160. function update(ii, ri) {
  4161. if (faceted) {
  4162. if (f) f = cancelAnimationFrame(f);
  4163. if (ii == null) facetState.delete(index.fi);
  4164. else {
  4165. facetState.set(index.fi, ri);
  4166. f = requestAnimationFrame(() => {
  4167. f = null;
  4168. for (const [fi, r] of facetState) {
  4169. if (r < ri || (r === ri && fi < index.fi)) {
  4170. ii = null;
  4171. break;
  4172. }
  4173. }
  4174. render(ii);
  4175. });
  4176. return;
  4177. }
  4178. }
  4179. render(ii);
  4180. }
  4181. function render(ii) {
  4182. if (i === ii && s === state.sticky) return; // the tooltip hasn’t moved
  4183. i = ii;
  4184. s = context.pointerSticky = state.sticky;
  4185. const I = i == null ? [] : [i];
  4186. if (faceted) (I.fx = index.fx), (I.fy = index.fy), (I.fi = index.fi);
  4187. const r = next(I, scales, values, dimensions, context);
  4188. if (g) {
  4189. // When faceting, preserve swapped mark and facet transforms; also
  4190. // remove ARIA attributes since these are promoted to the parent. This
  4191. // is perhaps brittle in that it depends on how Plot renders facets,
  4192. // but it produces a cleaner and more accessible SVG structure.
  4193. if (faceted) {
  4194. const p = g.parentNode;
  4195. const ft = g.getAttribute("transform");
  4196. const mt = r.getAttribute("transform");
  4197. ft ? r.setAttribute("transform", ft) : r.removeAttribute("transform");
  4198. mt ? p.setAttribute("transform", mt) : p.removeAttribute("transform");
  4199. r.removeAttribute("aria-label");
  4200. r.removeAttribute("aria-description");
  4201. r.removeAttribute("aria-hidden");
  4202. }
  4203. g.replaceWith(r);
  4204. }
  4205. state.roots[renderIndex] = g = r;
  4206. // Dispatch the value. When simultaneously exiting this facet and
  4207. // entering a new one, prioritize the entering facet.
  4208. if (!(i == null && facetState?.size > 1)) {
  4209. const value = i == null ? null : isArray(data) ? data[i] : data.get(i);
  4210. context.dispatchValue(value);
  4211. }
  4212. return r;
  4213. }
  4214. // Select the closest point to the mouse in the current facet; for
  4215. // pointerX or pointerY, the orthogonal component of the distance is
  4216. // squashed, selecting primarily on the dominant dimension. Across facets,
  4217. // use unsquashed distance to determine the winner.
  4218. function pointermove(event) {
  4219. if (state.sticky || (event.pointerType === "mouse" && event.buttons === 1)) return; // dragging
  4220. let [xp, yp] = d3.pointer(event);
  4221. (xp -= tx), (yp -= ty); // correct for facets and band scales
  4222. const kpx = xp < dimensions.marginLeft || xp > dimensions.width - dimensions.marginRight ? 1 : kx;
  4223. const kpy = yp < dimensions.marginTop || yp > dimensions.height - dimensions.marginBottom ? 1 : ky;
  4224. let ii = null;
  4225. let ri = maxRadius * maxRadius;
  4226. for (const j of index) {
  4227. const dx = kpx * (px(j) - xp);
  4228. const dy = kpy * (py(j) - yp);
  4229. const rj = dx * dx + dy * dy;
  4230. if (rj <= ri) (ii = j), (ri = rj);
  4231. }
  4232. if (ii != null && (kx !== 1 || ky !== 1)) {
  4233. const dx = px(ii) - xp;
  4234. const dy = py(ii) - yp;
  4235. ri = dx * dx + dy * dy;
  4236. }
  4237. update(ii, ri);
  4238. }
  4239. function pointerdown(event) {
  4240. if (event.pointerType !== "mouse") return;
  4241. if (i == null) return; // not pointing
  4242. if (state.sticky && state.roots.some((r) => r?.contains(event.target))) return; // stay sticky
  4243. if (state.sticky) (state.sticky = false), state.renders.forEach((r) => r(null)); // clear all pointers
  4244. else (state.sticky = true), render(i);
  4245. event.stopImmediatePropagation(); // suppress other pointers
  4246. }
  4247. function pointerleave(event) {
  4248. if (event.pointerType !== "mouse") return;
  4249. if (!state.sticky) update(null);
  4250. }
  4251. // We listen to the svg element; listening to the window instead would let
  4252. // us receive pointer events from farther away, but would also make it
  4253. // hard to know when to remove the listeners. (Using a mutation observer
  4254. // to watch the entire document is likely too expensive.)
  4255. svg.addEventListener("pointerenter", pointermove);
  4256. svg.addEventListener("pointermove", pointermove);
  4257. svg.addEventListener("pointerdown", pointerdown);
  4258. svg.addEventListener("pointerleave", pointerleave);
  4259. return render(null);
  4260. }, render)
  4261. };
  4262. }
  4263. function pointer(options) {
  4264. return pointerK(1, 1, options);
  4265. }
  4266. function pointerX(options) {
  4267. return pointerK(1, 0.01, options);
  4268. }
  4269. function pointerY(options) {
  4270. return pointerK(0.01, 1, options);
  4271. }
  4272. function anchorX$1({x1: X1, x2: X2, x: X = X1}, cx) {
  4273. return X1 && X2 ? (i) => (X1[i] + X2[i]) / 2 : X ? (i) => X[i] : () => cx;
  4274. }
  4275. function anchorY$1({y1: Y1, y2: Y2, y: Y = Y1}, cy) {
  4276. return Y1 && Y2 ? (i) => (Y1[i] + Y2[i]) / 2 : Y ? (i) => Y[i] : () => cy;
  4277. }
  4278. function inferFontVariant$2(scale) {
  4279. return isOrdinalScale(scale) && scale.interval === undefined ? undefined : "tabular-nums";
  4280. }
  4281. function legendRamp(color, options) {
  4282. let {
  4283. label = color.label,
  4284. tickSize = 6,
  4285. width = 240,
  4286. height = 44 + tickSize,
  4287. marginTop = 18,
  4288. marginRight = 0,
  4289. marginBottom = 16 + tickSize,
  4290. marginLeft = 0,
  4291. style,
  4292. ticks = (width - marginLeft - marginRight) / 64,
  4293. tickFormat,
  4294. fontVariant = inferFontVariant$2(color),
  4295. round = true,
  4296. opacity,
  4297. className
  4298. } = options;
  4299. const context = createContext(options);
  4300. className = maybeClassName(className);
  4301. opacity = maybeNumberChannel(opacity)[1];
  4302. if (tickFormat === null) tickFormat = () => null;
  4303. const svg = create("svg", context)
  4304. .attr("class", `${className}-ramp`)
  4305. .attr("font-family", "system-ui, sans-serif")
  4306. .attr("font-size", 10)
  4307. .attr("width", width)
  4308. .attr("height", height)
  4309. .attr("viewBox", `0 0 ${width} ${height}`)
  4310. .call((svg) =>
  4311. // Warning: if you edit this, change defaultClassName.
  4312. svg.append("style").text(
  4313. `:where(.${className}-ramp) {
  4314. display: block;
  4315. height: auto;
  4316. height: intrinsic;
  4317. max-width: 100%;
  4318. overflow: visible;
  4319. }
  4320. :where(.${className}-ramp text) {
  4321. white-space: pre;
  4322. }`
  4323. )
  4324. )
  4325. .call(applyInlineStyles, style);
  4326. let tickAdjust = (g) => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height);
  4327. let x;
  4328. // Some D3 scales use scale.interpolate, some scale.interpolator, and some
  4329. // scale.round; this normalizes the API so it works with all scale types.
  4330. const applyRange = round ? (x, range) => x.rangeRound(range) : (x, range) => x.range(range);
  4331. const {type, domain, range, interpolate, scale, pivot} = color;
  4332. // Continuous
  4333. if (interpolate) {
  4334. // Often interpolate is a “fixed” interpolator on the [0, 1] interval, as
  4335. // with a built-in color scheme, but sometimes it is a function that takes
  4336. // two arguments and is used in conjunction with the range.
  4337. const interpolator =
  4338. range === undefined
  4339. ? interpolate
  4340. : d3.piecewise(interpolate.length === 1 ? interpolatePiecewise(interpolate) : interpolate, range);
  4341. // Construct a D3 scale of the same type, but with a range that evenly
  4342. // divides the horizontal extent of the legend. (In the common case, the
  4343. // domain.length is two, and so the range is simply the extent.) For a
  4344. // diverging scale, we need an extra point in the range for the pivot such
  4345. // that the pivot is always drawn in the middle.
  4346. x = applyRange(
  4347. scale.copy(),
  4348. d3.quantize(
  4349. d3.interpolateNumber(marginLeft, width - marginRight),
  4350. Math.min(domain.length + (pivot !== undefined), range === undefined ? Infinity : range.length)
  4351. )
  4352. );
  4353. // Construct a 256×1 canvas, filling each pixel using the interpolator.
  4354. const n = 256;
  4355. const canvas = context.document.createElement("canvas");
  4356. canvas.width = n;
  4357. canvas.height = 1;
  4358. const context2 = canvas.getContext("2d");
  4359. for (let i = 0, j = n - 1; i < n; ++i) {
  4360. context2.fillStyle = interpolator(i / j);
  4361. context2.fillRect(i, 0, 1, 1);
  4362. }
  4363. svg
  4364. .append("image")
  4365. .attr("opacity", opacity)
  4366. .attr("x", marginLeft)
  4367. .attr("y", marginTop)
  4368. .attr("width", width - marginLeft - marginRight)
  4369. .attr("height", height - marginTop - marginBottom)
  4370. .attr("preserveAspectRatio", "none")
  4371. .attr("xlink:href", canvas.toDataURL());
  4372. }
  4373. // Threshold
  4374. else if (type === "threshold") {
  4375. const thresholds = domain;
  4376. const thresholdFormat =
  4377. tickFormat === undefined ? (d) => d : typeof tickFormat === "string" ? d3.format(tickFormat) : tickFormat;
  4378. // Construct a linear scale with evenly-spaced ticks for each of the
  4379. // thresholds; the domain extends one beyond the threshold extent.
  4380. x = applyRange(d3.scaleLinear().domain([-1, range.length - 1]), [marginLeft, width - marginRight]);
  4381. svg
  4382. .append("g")
  4383. .attr("fill-opacity", opacity)
  4384. .selectAll()
  4385. .data(range)
  4386. .enter()
  4387. .append("rect")
  4388. .attr("x", (d, i) => x(i - 1))
  4389. .attr("y", marginTop)
  4390. .attr("width", (d, i) => x(i) - x(i - 1))
  4391. .attr("height", height - marginTop - marginBottom)
  4392. .attr("fill", (d) => d);
  4393. ticks = map$1(thresholds, (_, i) => i);
  4394. tickFormat = (i) => thresholdFormat(thresholds[i], i);
  4395. }
  4396. // Ordinal (hopefully!)
  4397. else {
  4398. x = applyRange(d3.scaleBand().domain(domain), [marginLeft, width - marginRight]);
  4399. svg
  4400. .append("g")
  4401. .attr("fill-opacity", opacity)
  4402. .selectAll()
  4403. .data(domain)
  4404. .enter()
  4405. .append("rect")
  4406. .attr("x", x)
  4407. .attr("y", marginTop)
  4408. .attr("width", Math.max(0, x.bandwidth() - 1))
  4409. .attr("height", height - marginTop - marginBottom)
  4410. .attr("fill", scale);
  4411. tickAdjust = () => {};
  4412. }
  4413. svg
  4414. .append("g")
  4415. .attr("transform", `translate(0,${height - marginBottom})`)
  4416. .call(
  4417. d3.axisBottom(x)
  4418. .ticks(Array.isArray(ticks) ? null : ticks, typeof tickFormat === "string" ? tickFormat : undefined)
  4419. .tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
  4420. .tickSize(tickSize)
  4421. .tickValues(Array.isArray(ticks) ? ticks : null)
  4422. )
  4423. .attr("font-size", null)
  4424. .attr("font-family", null)
  4425. .attr("font-variant", impliedString(fontVariant, "normal"))
  4426. .call(tickAdjust)
  4427. .call((g) => g.select(".domain").remove());
  4428. if (label !== undefined) {
  4429. svg
  4430. .append("text")
  4431. .attr("x", marginLeft)
  4432. .attr("y", marginTop - 6)
  4433. .attr("fill", "currentColor") // TODO move to stylesheet?
  4434. .attr("font-weight", "bold")
  4435. .text(label);
  4436. }
  4437. return svg.node();
  4438. }
  4439. const radians = Math.PI / 180;
  4440. function markers(mark, {marker, markerStart = marker, markerMid = marker, markerEnd = marker} = {}) {
  4441. mark.markerStart = maybeMarker(markerStart);
  4442. mark.markerMid = maybeMarker(markerMid);
  4443. mark.markerEnd = maybeMarker(markerEnd);
  4444. }
  4445. function maybeMarker(marker) {
  4446. if (marker == null || marker === false) return null;
  4447. if (marker === true) return markerCircleFill;
  4448. if (typeof marker === "function") return marker;
  4449. switch (`${marker}`.toLowerCase()) {
  4450. case "none":
  4451. return null;
  4452. case "arrow":
  4453. return markerArrow("auto");
  4454. case "arrow-reverse":
  4455. return markerArrow("auto-start-reverse");
  4456. case "dot":
  4457. return markerDot;
  4458. case "circle":
  4459. case "circle-fill":
  4460. return markerCircleFill;
  4461. case "circle-stroke":
  4462. return markerCircleStroke;
  4463. case "tick":
  4464. return markerTick("auto");
  4465. case "tick-x":
  4466. return markerTick(90);
  4467. case "tick-y":
  4468. return markerTick(0);
  4469. }
  4470. throw new Error(`invalid marker: ${marker}`);
  4471. }
  4472. function markerArrow(orient) {
  4473. return (color, context) =>
  4474. create("svg:marker", context)
  4475. .attr("viewBox", "-5 -5 10 10")
  4476. .attr("markerWidth", 6.67)
  4477. .attr("markerHeight", 6.67)
  4478. .attr("orient", orient)
  4479. .attr("fill", "none")
  4480. .attr("stroke", color)
  4481. .attr("stroke-width", 1.5)
  4482. .attr("stroke-linecap", "round")
  4483. .attr("stroke-linejoin", "round")
  4484. .call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3"))
  4485. .node();
  4486. }
  4487. function markerDot(color, context) {
  4488. return create("svg:marker", context)
  4489. .attr("viewBox", "-5 -5 10 10")
  4490. .attr("markerWidth", 6.67)
  4491. .attr("markerHeight", 6.67)
  4492. .attr("fill", color)
  4493. .attr("stroke", "none")
  4494. .call((marker) => marker.append("circle").attr("r", 2.5))
  4495. .node();
  4496. }
  4497. function markerCircleFill(color, context) {
  4498. return create("svg:marker", context)
  4499. .attr("viewBox", "-5 -5 10 10")
  4500. .attr("markerWidth", 6.67)
  4501. .attr("markerHeight", 6.67)
  4502. .attr("fill", color)
  4503. .attr("stroke", "var(--plot-background)")
  4504. .attr("stroke-width", 1.5)
  4505. .call((marker) => marker.append("circle").attr("r", 3))
  4506. .node();
  4507. }
  4508. function markerCircleStroke(color, context) {
  4509. return create("svg:marker", context)
  4510. .attr("viewBox", "-5 -5 10 10")
  4511. .attr("markerWidth", 6.67)
  4512. .attr("markerHeight", 6.67)
  4513. .attr("fill", "var(--plot-background)")
  4514. .attr("stroke", color)
  4515. .attr("stroke-width", 1.5)
  4516. .call((marker) => marker.append("circle").attr("r", 3))
  4517. .node();
  4518. }
  4519. function markerTick(orient) {
  4520. return (color, context) =>
  4521. create("svg:marker", context)
  4522. .attr("viewBox", "-3 -3 6 6")
  4523. .attr("markerWidth", 6)
  4524. .attr("markerHeight", 6)
  4525. .attr("orient", orient)
  4526. .attr("stroke", color)
  4527. .call((marker) => marker.append("path").attr("d", "M0,-3v6"))
  4528. .node();
  4529. }
  4530. let nextMarkerId = 0;
  4531. function applyMarkers(path, mark, {stroke: S}, context) {
  4532. return applyMarkersColor(path, mark, S && ((i) => S[i]), null, context);
  4533. }
  4534. function applyGroupedMarkers(path, mark, {stroke: S, z: Z}, context) {
  4535. return applyMarkersColor(path, mark, S && (([i]) => S[i]), Z, context);
  4536. }
  4537. const START = 1;
  4538. const END = 2;
  4539. /**
  4540. * When rendering lines or areas with variable aesthetics, a single series
  4541. * produces multiple path elements. The first path element is a START segment;
  4542. * the last path element is an END segment. When there is only a single path
  4543. * element, it is both a START and an END segment.
  4544. */
  4545. function getGroupedOrientation(path, Z) {
  4546. const O = new Uint8Array(Z.length);
  4547. const D = path.data().filter((I) => I.length > 1);
  4548. const n = D.length;
  4549. // Forward pass to find start segments.
  4550. for (let i = 0, z = unset; i < n; ++i) {
  4551. const I = D[i];
  4552. if (I.length > 1) {
  4553. const i = I[0];
  4554. if (z !== (z = keyof(Z[i]))) O[i] |= START;
  4555. }
  4556. }
  4557. // Backwards pass to find end segments.
  4558. for (let i = n - 1, z = unset; i >= 0; --i) {
  4559. const I = D[i];
  4560. if (I.length > 1) {
  4561. const i = I[0];
  4562. if (z !== (z = keyof(Z[i]))) O[i] |= END;
  4563. }
  4564. }
  4565. return ([i]) => O[i];
  4566. }
  4567. function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, strokeof = () => stroke, Z, context) {
  4568. if (!markerStart && !markerMid && !markerEnd) return;
  4569. const iriByMarkerColor = new Map();
  4570. const orient = Z && getGroupedOrientation(path, Z);
  4571. function applyMarker(name, marker, filter) {
  4572. return function (i) {
  4573. if (filter && !filter(i)) return;
  4574. const color = strokeof(i);
  4575. let iriByColor = iriByMarkerColor.get(marker);
  4576. if (!iriByColor) iriByMarkerColor.set(marker, (iriByColor = new Map()));
  4577. let iri = iriByColor.get(color);
  4578. if (!iri) {
  4579. const node = this.parentNode.insertBefore(marker(color, context), this);
  4580. const id = `plot-marker-${++nextMarkerId}`;
  4581. node.setAttribute("id", id);
  4582. iriByColor.set(color, (iri = `url(#${id})`));
  4583. }
  4584. this.setAttribute(name, iri);
  4585. };
  4586. }
  4587. if (markerStart) path.each(applyMarker("marker-start", markerStart, orient && ((i) => orient(i) & START)));
  4588. if (markerMid && orient) path.each(applyMarker("marker-start", markerMid, (i) => !(orient(i) & START)));
  4589. if (markerMid) path.each(applyMarker("marker-mid", markerMid));
  4590. if (markerEnd) path.each(applyMarker("marker-end", markerEnd, orient && ((i) => orient(i) & END)));
  4591. }
  4592. function maybeInsetX({inset, insetLeft, insetRight, ...options} = {}) {
  4593. [insetLeft, insetRight] = maybeInset(inset, insetLeft, insetRight);
  4594. return {inset, insetLeft, insetRight, ...options};
  4595. }
  4596. function maybeInsetY({inset, insetTop, insetBottom, ...options} = {}) {
  4597. [insetTop, insetBottom] = maybeInset(inset, insetTop, insetBottom);
  4598. return {inset, insetTop, insetBottom, ...options};
  4599. }
  4600. function maybeInset(inset, inset1, inset2) {
  4601. return inset === undefined && inset1 === undefined && inset2 === undefined
  4602. ? offset
  4603. ? [1, 0]
  4604. : [0.5, 0.5]
  4605. : [inset1, inset2];
  4606. }
  4607. // The interval may be specified either as x: {value, interval} or as {x,
  4608. // interval}. The former can be used to specify separate intervals for x and y,
  4609. // for example with Plot.rect.
  4610. function maybeIntervalValue(value, {interval}) {
  4611. value = {...maybeValue(value)};
  4612. value.interval = maybeInterval(value.interval === undefined ? interval : value.interval);
  4613. return value;
  4614. }
  4615. function maybeIntervalK(k, maybeInsetK, options, trivial) {
  4616. const {[k]: v, [`${k}1`]: v1, [`${k}2`]: v2} = options;
  4617. const {value, interval} = maybeIntervalValue(v, options);
  4618. if (value == null || (interval == null && !trivial)) return options;
  4619. const label = labelof(v);
  4620. if (interval == null) {
  4621. let V;
  4622. const kv = {transform: (data) => V || (V = valueof(data, value)), label};
  4623. return {
  4624. ...options,
  4625. [k]: undefined,
  4626. [`${k}1`]: v1 === undefined ? kv : v1,
  4627. [`${k}2`]: v2 === undefined && !(v1 === v2 && trivial) ? kv : v2
  4628. };
  4629. }
  4630. let D1, V1;
  4631. function transform(data) {
  4632. if (V1 !== undefined && data === D1) return V1; // memoize
  4633. return (V1 = map$1(valueof((D1 = data), value), (v) => interval.floor(v)));
  4634. }
  4635. return maybeInsetK({
  4636. ...options,
  4637. [k]: undefined,
  4638. [`${k}1`]: v1 === undefined ? {transform, label} : v1,
  4639. [`${k}2`]: v2 === undefined ? {transform: (data) => transform(data).map((v) => interval.offset(v)), label} : v2
  4640. });
  4641. }
  4642. function maybeIntervalMidK(k, maybeInsetK, options) {
  4643. const {[k]: v} = options;
  4644. const {value, interval} = maybeIntervalValue(v, options);
  4645. if (value == null || interval == null) return options;
  4646. return maybeInsetK({
  4647. ...options,
  4648. [k]: {
  4649. label: labelof(v),
  4650. transform: (data) => {
  4651. const V1 = map$1(valueof(data, value), (v) => interval.floor(v));
  4652. const V2 = V1.map((v) => interval.offset(v));
  4653. return V1.map(
  4654. isTemporal(V1)
  4655. ? (v1, v2) =>
  4656. v1 == null || isNaN((v1 = +v1)) || ((v2 = V2[v2]), v2 == null) || isNaN((v2 = +v2))
  4657. ? undefined
  4658. : new Date((v1 + v2) / 2)
  4659. : (v1, v2) => (v1 == null || ((v2 = V2[v2]), v2 == null) ? NaN : (+v1 + +v2) / 2)
  4660. );
  4661. }
  4662. }
  4663. });
  4664. }
  4665. function maybeTrivialIntervalX(options = {}) {
  4666. return maybeIntervalK("x", maybeInsetX, options, true);
  4667. }
  4668. function maybeTrivialIntervalY(options = {}) {
  4669. return maybeIntervalK("y", maybeInsetY, options, true);
  4670. }
  4671. function maybeIntervalX(options = {}) {
  4672. return maybeIntervalK("x", maybeInsetX, options);
  4673. }
  4674. function maybeIntervalY(options = {}) {
  4675. return maybeIntervalK("y", maybeInsetY, options);
  4676. }
  4677. function maybeIntervalMidX(options = {}) {
  4678. return maybeIntervalMidK("x", maybeInsetX, options);
  4679. }
  4680. function maybeIntervalMidY(options = {}) {
  4681. return maybeIntervalMidK("y", maybeInsetY, options);
  4682. }
  4683. const defaults$k = {
  4684. ariaLabel: "rule",
  4685. fill: null,
  4686. stroke: "currentColor"
  4687. };
  4688. class RuleX extends Mark {
  4689. constructor(data, options = {}) {
  4690. const {x, y1, y2, inset = 0, insetTop = inset, insetBottom = inset} = options;
  4691. super(
  4692. data,
  4693. {
  4694. x: {value: x, scale: "x", optional: true},
  4695. y1: {value: y1, scale: "y", optional: true},
  4696. y2: {value: y2, scale: "y", optional: true}
  4697. },
  4698. withTip(options, "x"),
  4699. defaults$k
  4700. );
  4701. this.insetTop = number$1(insetTop);
  4702. this.insetBottom = number$1(insetBottom);
  4703. markers(this, options);
  4704. }
  4705. render(index, scales, channels, dimensions, context) {
  4706. const {x, y} = scales;
  4707. const {x: X, y1: Y1, y2: Y2} = channels;
  4708. const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
  4709. const {insetTop, insetBottom} = this;
  4710. return create("svg:g", context)
  4711. .call(applyIndirectStyles, this, dimensions, context)
  4712. .call(applyTransform, this, {x: X && x}, offset, 0)
  4713. .call((g) =>
  4714. g
  4715. .selectAll()
  4716. .data(index)
  4717. .enter()
  4718. .append("line")
  4719. .call(applyDirectStyles, this)
  4720. .attr("x1", X ? (i) => X[i] : (marginLeft + width - marginRight) / 2)
  4721. .attr("x2", X ? (i) => X[i] : (marginLeft + width - marginRight) / 2)
  4722. .attr("y1", Y1 && !isCollapsed(y) ? (i) => Y1[i] + insetTop : marginTop + insetTop)
  4723. .attr(
  4724. "y2",
  4725. Y2 && !isCollapsed(y)
  4726. ? y.bandwidth
  4727. ? (i) => Y2[i] + y.bandwidth() - insetBottom
  4728. : (i) => Y2[i] - insetBottom
  4729. : height - marginBottom - insetBottom
  4730. )
  4731. .call(applyChannelStyles, this, channels)
  4732. .call(applyMarkers, this, channels, context)
  4733. )
  4734. .node();
  4735. }
  4736. }
  4737. class RuleY extends Mark {
  4738. constructor(data, options = {}) {
  4739. const {x1, x2, y, inset = 0, insetRight = inset, insetLeft = inset} = options;
  4740. super(
  4741. data,
  4742. {
  4743. y: {value: y, scale: "y", optional: true},
  4744. x1: {value: x1, scale: "x", optional: true},
  4745. x2: {value: x2, scale: "x", optional: true}
  4746. },
  4747. withTip(options, "y"),
  4748. defaults$k
  4749. );
  4750. this.insetRight = number$1(insetRight);
  4751. this.insetLeft = number$1(insetLeft);
  4752. markers(this, options);
  4753. }
  4754. render(index, scales, channels, dimensions, context) {
  4755. const {x, y} = scales;
  4756. const {y: Y, x1: X1, x2: X2} = channels;
  4757. const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions;
  4758. const {insetLeft, insetRight} = this;
  4759. return create("svg:g", context)
  4760. .call(applyIndirectStyles, this, dimensions, context)
  4761. .call(applyTransform, this, {y: Y && y}, 0, offset)
  4762. .call((g) =>
  4763. g
  4764. .selectAll()
  4765. .data(index)
  4766. .enter()
  4767. .append("line")
  4768. .call(applyDirectStyles, this)
  4769. .attr("x1", X1 && !isCollapsed(x) ? (i) => X1[i] + insetLeft : marginLeft + insetLeft)
  4770. .attr(
  4771. "x2",
  4772. X2 && !isCollapsed(x)
  4773. ? x.bandwidth
  4774. ? (i) => X2[i] + x.bandwidth() - insetRight
  4775. : (i) => X2[i] - insetRight
  4776. : width - marginRight - insetRight
  4777. )
  4778. .attr("y1", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2)
  4779. .attr("y2", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2)
  4780. .call(applyChannelStyles, this, channels)
  4781. .call(applyMarkers, this, channels, context)
  4782. )
  4783. .node();
  4784. }
  4785. }
  4786. function ruleX(data, options) {
  4787. let {x = identity$1, y, y1, y2, ...rest} = maybeIntervalY(options);
  4788. [y1, y2] = maybeOptionalZero(y, y1, y2);
  4789. return new RuleX(data, {...rest, x, y1, y2});
  4790. }
  4791. function ruleY(data, options) {
  4792. let {y = identity$1, x, x1, x2, ...rest} = maybeIntervalX(options);
  4793. [x1, x2] = maybeOptionalZero(x, x1, x2);
  4794. return new RuleY(data, {...rest, y, x1, x2});
  4795. }
  4796. // For marks specified either as [0, x] or [x1, x2], or nothing.
  4797. function maybeOptionalZero(x, x1, x2) {
  4798. if (x == null) {
  4799. if (x1 === undefined) {
  4800. if (x2 !== undefined) return [0, x2];
  4801. } else {
  4802. if (x2 === undefined) return [0, x1];
  4803. }
  4804. } else if (x1 === undefined) {
  4805. return x2 === undefined ? [0, x] : [x, x2];
  4806. } else if (x2 === undefined) {
  4807. return [x, x1];
  4808. }
  4809. return [x1, x2];
  4810. }
  4811. function template(strings, ...parts) {
  4812. let n = parts.length;
  4813. // If any of the interpolated parameters are strings rather than functions,
  4814. // bake them into the template to optimize performance during render.
  4815. for (let j = 0, copy = true; j < n; ++j) {
  4816. if (typeof parts[j] !== "function") {
  4817. if (copy) {
  4818. strings = strings.slice(); // copy before mutate
  4819. copy = false;
  4820. }
  4821. strings.splice(j, 2, strings[j] + parts[j] + strings[j + 1]);
  4822. parts.splice(j, 1);
  4823. --j, --n;
  4824. }
  4825. }
  4826. return (i) => {
  4827. let s = strings[0];
  4828. for (let j = 0; j < n; ++j) {
  4829. s += parts[j](i) + strings[j + 1];
  4830. }
  4831. return s;
  4832. };
  4833. }
  4834. const defaults$j = {
  4835. ariaLabel: "text",
  4836. strokeLinejoin: "round",
  4837. strokeWidth: 3,
  4838. paintOrder: "stroke"
  4839. };
  4840. const softHyphen = "\u00ad";
  4841. class Text extends Mark {
  4842. constructor(data, options = {}) {
  4843. const {
  4844. x,
  4845. y,
  4846. text = isIterable(data) && isTextual(data) ? identity$1 : indexOf,
  4847. frameAnchor,
  4848. textAnchor = /right$/i.test(frameAnchor) ? "end" : /left$/i.test(frameAnchor) ? "start" : "middle",
  4849. lineAnchor = /^top/i.test(frameAnchor) ? "top" : /^bottom/i.test(frameAnchor) ? "bottom" : "middle",
  4850. lineHeight = 1,
  4851. lineWidth = Infinity,
  4852. textOverflow,
  4853. monospace,
  4854. fontFamily = monospace ? "ui-monospace, monospace" : undefined,
  4855. fontSize,
  4856. fontStyle,
  4857. fontVariant,
  4858. fontWeight,
  4859. rotate
  4860. } = options;
  4861. const [vrotate, crotate] = maybeNumberChannel(rotate, 0);
  4862. const [vfontSize, cfontSize] = maybeFontSizeChannel(fontSize);
  4863. super(
  4864. data,
  4865. {
  4866. x: {value: x, scale: "x", optional: true},
  4867. y: {value: y, scale: "y", optional: true},
  4868. fontSize: {value: vfontSize, optional: true},
  4869. rotate: {value: numberChannel(vrotate), optional: true},
  4870. text: {value: text, filter: nonempty, optional: true}
  4871. },
  4872. options,
  4873. defaults$j
  4874. );
  4875. this.rotate = crotate;
  4876. this.textAnchor = impliedString(textAnchor, "middle");
  4877. this.lineAnchor = keyword(lineAnchor, "lineAnchor", ["top", "middle", "bottom"]);
  4878. this.lineHeight = +lineHeight;
  4879. this.lineWidth = +lineWidth;
  4880. this.textOverflow = maybeTextOverflow(textOverflow);
  4881. this.monospace = !!monospace;
  4882. this.fontFamily = string(fontFamily);
  4883. this.fontSize = cfontSize;
  4884. this.fontStyle = string(fontStyle);
  4885. this.fontVariant = string(fontVariant);
  4886. this.fontWeight = string(fontWeight);
  4887. this.frameAnchor = maybeFrameAnchor(frameAnchor);
  4888. if (!(this.lineWidth >= 0)) throw new Error(`invalid lineWidth: ${lineWidth}`);
  4889. this.splitLines = splitter(this);
  4890. this.clipLine = clipper(this);
  4891. }
  4892. render(index, scales, channels, dimensions, context) {
  4893. const {x, y} = scales;
  4894. const {x: X, y: Y, rotate: R, text: T, title: TL, fontSize: FS} = channels;
  4895. const {rotate} = this;
  4896. const [cx, cy] = applyFrameAnchor(this, dimensions);
  4897. return create("svg:g", context)
  4898. .call(applyIndirectStyles, this, dimensions, context)
  4899. .call(applyIndirectTextStyles, this, T, dimensions)
  4900. .call(applyTransform, this, {x: X && x, y: Y && y})
  4901. .call((g) =>
  4902. g
  4903. .selectAll()
  4904. .data(index)
  4905. .enter()
  4906. .append("text")
  4907. .call(applyDirectStyles, this)
  4908. .call(applyMultilineText, this, T, TL)
  4909. .attr(
  4910. "transform",
  4911. template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${
  4912. R ? (i) => ` rotate(${R[i]})` : rotate ? ` rotate(${rotate})` : ``
  4913. }`
  4914. )
  4915. .call(applyAttr, "font-size", FS && ((i) => FS[i]))
  4916. .call(applyChannelStyles, this, channels)
  4917. )
  4918. .node();
  4919. }
  4920. }
  4921. function maybeTextOverflow(textOverflow) {
  4922. return textOverflow == null
  4923. ? null
  4924. : keyword(textOverflow, "textOverflow", [
  4925. "clip", // shorthand for clip-end
  4926. "ellipsis", // … ellipsis-end
  4927. "clip-start",
  4928. "clip-end",
  4929. "ellipsis-start",
  4930. "ellipsis-middle",
  4931. "ellipsis-end"
  4932. ]).replace(/^(clip|ellipsis)$/, "$1-end");
  4933. }
  4934. function applyMultilineText(selection, mark, T, TL) {
  4935. if (!T) return;
  4936. const {lineAnchor, lineHeight, textOverflow, splitLines, clipLine} = mark;
  4937. selection.each(function (i) {
  4938. const lines = splitLines(formatDefault(T[i]) ?? "").map(clipLine);
  4939. const n = lines.length;
  4940. const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? 1 - n : (164 - n * 100) / 200;
  4941. if (n > 1) {
  4942. let m = 0;
  4943. for (let i = 0; i < n; ++i) {
  4944. ++m;
  4945. if (!lines[i]) continue;
  4946. const tspan = this.ownerDocument.createElementNS(d3.namespaces.svg, "tspan");
  4947. tspan.setAttribute("x", 0);
  4948. if (i === m - 1) tspan.setAttribute("y", `${(y + i) * lineHeight}em`);
  4949. else tspan.setAttribute("dy", `${m * lineHeight}em`);
  4950. tspan.textContent = lines[i];
  4951. this.appendChild(tspan);
  4952. m = 0;
  4953. }
  4954. } else {
  4955. if (y) this.setAttribute("y", `${y * lineHeight}em`);
  4956. this.textContent = lines[0];
  4957. }
  4958. if (textOverflow && !TL && lines[0] !== T[i]) {
  4959. const title = this.ownerDocument.createElementNS(d3.namespaces.svg, "title");
  4960. title.textContent = T[i];
  4961. this.appendChild(title);
  4962. }
  4963. });
  4964. }
  4965. function text(data, {x, y, ...options} = {}) {
  4966. if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y);
  4967. return new Text(data, {...options, x, y});
  4968. }
  4969. function textX(data, {x = identity$1, ...options} = {}) {
  4970. return new Text(data, maybeIntervalMidY({...options, x}));
  4971. }
  4972. function textY(data, {y = identity$1, ...options} = {}) {
  4973. return new Text(data, maybeIntervalMidX({...options, y}));
  4974. }
  4975. function applyIndirectTextStyles(selection, mark, T) {
  4976. applyAttr(selection, "text-anchor", mark.textAnchor);
  4977. applyAttr(selection, "font-family", mark.fontFamily);
  4978. applyAttr(selection, "font-size", mark.fontSize);
  4979. applyAttr(selection, "font-style", mark.fontStyle);
  4980. applyAttr(selection, "font-variant", mark.fontVariant === undefined ? inferFontVariant$1(T) : mark.fontVariant);
  4981. applyAttr(selection, "font-weight", mark.fontWeight);
  4982. }
  4983. function inferFontVariant$1(T) {
  4984. return T && (isNumeric(T) || isTemporal(T)) ? "tabular-nums" : undefined;
  4985. }
  4986. // https://developer.mozilla.org/en-US/docs/Web/CSS/font-size
  4987. const fontSizes = new Set([
  4988. // global keywords
  4989. "inherit",
  4990. "initial",
  4991. "revert",
  4992. "unset",
  4993. // absolute keywords
  4994. "xx-small",
  4995. "x-small",
  4996. "small",
  4997. "medium",
  4998. "large",
  4999. "x-large",
  5000. "xx-large",
  5001. "xxx-large",
  5002. // relative keywords
  5003. "larger",
  5004. "smaller"
  5005. ]);
  5006. // The font size may be expressed as a constant in the following forms:
  5007. // - number in pixels
  5008. // - string keyword: see above
  5009. // - string <length>: e.g., "12px"
  5010. // - string <percentage>: e.g., "80%"
  5011. // Anything else is assumed to be a channel definition.
  5012. function maybeFontSizeChannel(fontSize) {
  5013. if (fontSize == null || typeof fontSize === "number") return [undefined, fontSize];
  5014. if (typeof fontSize !== "string") return [fontSize, undefined];
  5015. fontSize = fontSize.trim().toLowerCase();
  5016. return fontSizes.has(fontSize) || /^[+-]?\d*\.?\d+(e[+-]?\d+)?(\w*|%)$/.test(fontSize)
  5017. ? [undefined, fontSize]
  5018. : [fontSize, undefined];
  5019. }
  5020. // This is a greedy algorithm for line wrapping. It would be better to use the
  5021. // Knuth–Plass line breaking algorithm (but that would be much more complex).
  5022. // https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap
  5023. function lineWrap(input, maxWidth, widthof) {
  5024. const lines = [];
  5025. let lineStart,
  5026. lineEnd = 0;
  5027. for (const [wordStart, wordEnd, required] of lineBreaks(input)) {
  5028. // Record the start of a line. This isn’t the same as the previous line’s
  5029. // end because we often skip spaces between lines.
  5030. if (lineStart === undefined) lineStart = wordStart;
  5031. // If the current line is not empty, and if adding the current word would
  5032. // make the line longer than the allowed width, then break the line at the
  5033. // previous word end.
  5034. if (lineEnd > lineStart && widthof(input, lineStart, wordEnd) > maxWidth) {
  5035. lines.push(input.slice(lineStart, lineEnd) + (input[lineEnd - 1] === softHyphen ? "-" : ""));
  5036. lineStart = wordStart;
  5037. }
  5038. // If this is a required break (a newline), emit the line and reset.
  5039. if (required) {
  5040. lines.push(input.slice(lineStart, wordEnd));
  5041. lineStart = undefined;
  5042. continue;
  5043. }
  5044. // Extend the current line to include the new word.
  5045. lineEnd = wordEnd;
  5046. }
  5047. return lines;
  5048. }
  5049. // This is a rudimentary (and U.S.-centric) algorithm for finding opportunities
  5050. // to break lines between words. A better and far more comprehensive approach
  5051. // would be to use the official Unicode Line Breaking Algorithm.
  5052. // https://unicode.org/reports/tr14/
  5053. function* lineBreaks(input) {
  5054. let i = 0,
  5055. j = 0;
  5056. const n = input.length;
  5057. while (j < n) {
  5058. let k = 1;
  5059. switch (input[j]) {
  5060. case softHyphen:
  5061. case "-": // hyphen
  5062. ++j;
  5063. yield [i, j, false];
  5064. i = j;
  5065. break;
  5066. case " ":
  5067. yield [i, j, false];
  5068. while (input[++j] === " "); // skip multiple spaces
  5069. i = j;
  5070. break;
  5071. case "\r":
  5072. if (input[j + 1] === "\n") ++k; // falls through
  5073. case "\n":
  5074. yield [i, j, true];
  5075. j += k;
  5076. i = j;
  5077. break;
  5078. default:
  5079. ++j;
  5080. break;
  5081. }
  5082. }
  5083. yield [i, j, true];
  5084. }
  5085. // Computed as round(measureText(text).width * 10) at 10px system-ui. For
  5086. // characters that are not represented in this map, we’d ideally want to use a
  5087. // weighted average of what we expect to see. But since we don’t really know
  5088. // what that is, using “e” seems reasonable.
  5089. const defaultWidthMap = {
  5090. a: 56,
  5091. b: 63,
  5092. c: 57,
  5093. d: 63,
  5094. e: 58,
  5095. f: 37,
  5096. g: 62,
  5097. h: 60,
  5098. i: 26,
  5099. j: 26,
  5100. k: 55,
  5101. l: 26,
  5102. m: 88,
  5103. n: 60,
  5104. o: 60,
  5105. p: 62,
  5106. q: 62,
  5107. r: 39,
  5108. s: 54,
  5109. t: 38,
  5110. u: 60,
  5111. v: 55,
  5112. w: 79,
  5113. x: 54,
  5114. y: 55,
  5115. z: 55,
  5116. A: 69,
  5117. B: 67,
  5118. C: 73,
  5119. D: 74,
  5120. E: 61,
  5121. F: 58,
  5122. G: 76,
  5123. H: 75,
  5124. I: 28,
  5125. J: 55,
  5126. K: 67,
  5127. L: 58,
  5128. M: 89,
  5129. N: 75,
  5130. O: 78,
  5131. P: 65,
  5132. Q: 78,
  5133. R: 67,
  5134. S: 65,
  5135. T: 65,
  5136. U: 75,
  5137. V: 69,
  5138. W: 98,
  5139. X: 69,
  5140. Y: 67,
  5141. Z: 67,
  5142. 0: 64,
  5143. 1: 48,
  5144. 2: 62,
  5145. 3: 64,
  5146. 4: 66,
  5147. 5: 63,
  5148. 6: 65,
  5149. 7: 58,
  5150. 8: 65,
  5151. 9: 65,
  5152. " ": 29,
  5153. "!": 32,
  5154. '"': 49,
  5155. "'": 31,
  5156. "(": 39,
  5157. ")": 39,
  5158. ",": 31,
  5159. "-": 48,
  5160. ".": 31,
  5161. "/": 32,
  5162. ":": 31,
  5163. ";": 31,
  5164. "?": 52,
  5165. "‘": 31,
  5166. "’": 31,
  5167. "“": 47,
  5168. "”": 47,
  5169. "…": 82
  5170. };
  5171. // This is a rudimentary (and U.S.-centric) algorithm for measuring the width of
  5172. // a string based on a technique of Gregor Aisch; it assumes that individual
  5173. // characters are laid out independently and does not implement the Unicode
  5174. // grapheme cluster breaking algorithm. It does understand code points, though,
  5175. // and so treats things like emoji as having the width of a lowercase e (and
  5176. // should be equivalent to using for-of to iterate over code points, while also
  5177. // being fast). TODO Optimize this by noting that we often re-measure characters
  5178. // that were previously measured?
  5179. // http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
  5180. // https://exploringjs.com/impatient-js/ch_strings.html#atoms-of-text
  5181. function defaultWidth(text, start = 0, end = text.length) {
  5182. let sum = 0;
  5183. for (let i = start; i < end; i = readCharacter(text, i)) {
  5184. sum += defaultWidthMap[text[i]] ?? (isPictographic(text, i) ? 120 : defaultWidthMap.e);
  5185. }
  5186. return sum;
  5187. }
  5188. // Even for monospaced text, we can’t assume that the number of UTF-16 code
  5189. // points (i.e., the length of a string) corresponds to the number of visible
  5190. // characters; we still have to count graphemes. And note that pictographic
  5191. // characters such as emojis are typically not monospaced!
  5192. function monospaceWidth(text, start = 0, end = text.length) {
  5193. let sum = 0;
  5194. for (let i = start; i < end; i = readCharacter(text, i)) {
  5195. sum += isPictographic(text, i) ? 126 : 63;
  5196. }
  5197. return sum;
  5198. }
  5199. function splitter({monospace, lineWidth, textOverflow}) {
  5200. if (textOverflow != null || lineWidth == Infinity) return (text) => text.split(/\r\n?|\n/g);
  5201. const widthof = monospace ? monospaceWidth : defaultWidth;
  5202. const maxWidth = lineWidth * 100;
  5203. return (text) => lineWrap(text, maxWidth, widthof);
  5204. }
  5205. function clipper({monospace, lineWidth, textOverflow}) {
  5206. if (textOverflow == null || lineWidth == Infinity) return (text) => text;
  5207. const widthof = monospace ? monospaceWidth : defaultWidth;
  5208. const maxWidth = lineWidth * 100;
  5209. switch (textOverflow) {
  5210. case "clip-start":
  5211. return (text) => clipStart(text, maxWidth, widthof, "");
  5212. case "clip-end":
  5213. return (text) => clipEnd(text, maxWidth, widthof, "");
  5214. case "ellipsis-start":
  5215. return (text) => clipStart(text, maxWidth, widthof, ellipsis);
  5216. case "ellipsis-middle":
  5217. return (text) => clipMiddle(text, maxWidth, widthof, ellipsis);
  5218. case "ellipsis-end":
  5219. return (text) => clipEnd(text, maxWidth, widthof, ellipsis);
  5220. }
  5221. }
  5222. const ellipsis = "…";
  5223. // Cuts the given text to the given width, using the specified widthof function;
  5224. // the returned [index, error] guarantees text.slice(0, index) fits within the
  5225. // specified width with the given error. If the text fits naturally within the
  5226. // given width, returns [-1, 0]. If the text needs cutting, the given inset
  5227. // specifies how much space (in the same units as width and widthof) to reserve
  5228. // for a possible ellipsis character.
  5229. function cut(text, width, widthof, inset) {
  5230. const I = []; // indexes of read character boundaries
  5231. let w = 0; // current line width
  5232. for (let i = 0, j = 0, n = text.length; i < n; i = j) {
  5233. j = readCharacter(text, i); // read the next character
  5234. const l = widthof(text, i, j); // current character width
  5235. if (w + l > width) {
  5236. w += inset;
  5237. while (w > width && i > 0) (j = i), (i = I.pop()), (w -= widthof(text, i, j)); // remove excess
  5238. return [i, width - w];
  5239. }
  5240. w += l;
  5241. I.push(i);
  5242. }
  5243. return [-1, 0];
  5244. }
  5245. function clipEnd(text, width, widthof, ellipsis) {
  5246. text = text.trim(); // ignore leading and trailing whitespace
  5247. const e = widthof(ellipsis);
  5248. const [i] = cut(text, width, widthof, e);
  5249. return i < 0 ? text : text.slice(0, i).trimEnd() + ellipsis;
  5250. }
  5251. function clipMiddle(text, width, widthof, ellipsis) {
  5252. text = text.trim(); // ignore leading and trailing whitespace
  5253. const w = widthof(text);
  5254. if (w <= width) return text;
  5255. const e = widthof(ellipsis) / 2;
  5256. const [i, ei] = cut(text, width / 2, widthof, e);
  5257. const [j] = cut(text, w - width / 2 - ei + e, widthof, -e); // TODO read spaces?
  5258. return j < 0 ? ellipsis : text.slice(0, i).trimEnd() + ellipsis + text.slice(readCharacter(text, j)).trimStart();
  5259. }
  5260. function clipStart(text, width, widthof, ellipsis) {
  5261. text = text.trim(); // ignore leading and trailing whitespace
  5262. const w = widthof(text);
  5263. if (w <= width) return text;
  5264. const e = widthof(ellipsis);
  5265. const [j] = cut(text, w - width + e, widthof, -e); // TODO read spaces?
  5266. return j < 0 ? ellipsis : ellipsis + text.slice(readCharacter(text, j)).trimStart();
  5267. }
  5268. const reCombiner = /[\p{Combining_Mark}\p{Emoji_Modifier}]+/uy;
  5269. const rePictographic = /\p{Extended_Pictographic}/uy;
  5270. // Reads a single “character” element from the given text starting at the given
  5271. // index, returning the index after the read character. Ideally, this implements
  5272. // the Unicode text segmentation algorithm and understands grapheme cluster
  5273. // boundaries, etc., but in practice this is only smart enough to detect UTF-16
  5274. // surrogate pairs, combining marks, and zero-width joiner (zwj) sequences such
  5275. // as emoji skin color modifiers. https://unicode.org/reports/tr29/
  5276. function readCharacter(text, i) {
  5277. i += isSurrogatePair(text, i) ? 2 : 1;
  5278. if (isCombiner(text, i)) i = reCombiner.lastIndex;
  5279. if (isZeroWidthJoiner(text, i)) return readCharacter(text, i + 1);
  5280. return i;
  5281. }
  5282. // We avoid more expensive regex tests involving Unicode property classes by
  5283. // first checking for the common case of 7-bit ASCII characters.
  5284. function isAscii(text, i) {
  5285. return text.charCodeAt(i) < 0x80;
  5286. }
  5287. function isSurrogatePair(text, i) {
  5288. const hi = text.charCodeAt(i);
  5289. if (hi >= 0xd800 && hi < 0xdc00) {
  5290. const lo = text.charCodeAt(i + 1);
  5291. return lo >= 0xdc00 && lo < 0xe000;
  5292. }
  5293. return false;
  5294. }
  5295. function isZeroWidthJoiner(text, i) {
  5296. return text.charCodeAt(i) === 0x200d;
  5297. }
  5298. function isCombiner(text, i) {
  5299. return isAscii(text, i) ? false : ((reCombiner.lastIndex = i), reCombiner.test(text));
  5300. }
  5301. function isPictographic(text, i) {
  5302. return isAscii(text, i) ? false : ((rePictographic.lastIndex = i), rePictographic.test(text));
  5303. }
  5304. const defaults$i = {
  5305. ariaLabel: "vector",
  5306. fill: "none",
  5307. stroke: "currentColor",
  5308. strokeWidth: 1.5,
  5309. strokeLinejoin: "round",
  5310. strokeLinecap: "round"
  5311. };
  5312. const defaultRadius = 3.5;
  5313. // The size of the arrowhead is proportional to its length, but we still allow
  5314. // the relative size of the head to be controlled via the mark’s width option;
  5315. // doubling the default radius will produce an arrowhead that is twice as big.
  5316. // That said, we’ll probably want a arrow with a fixed head size, too.
  5317. const wingRatio = defaultRadius * 5;
  5318. const shapeArrow = {
  5319. draw(context, l, r) {
  5320. const wing = (l * r) / wingRatio;
  5321. context.moveTo(0, 0);
  5322. context.lineTo(0, -l);
  5323. context.moveTo(-wing, wing - l);
  5324. context.lineTo(0, -l);
  5325. context.lineTo(wing, wing - l);
  5326. }
  5327. };
  5328. const shapeSpike = {
  5329. draw(context, l, r) {
  5330. context.moveTo(-r, 0);
  5331. context.lineTo(0, -l);
  5332. context.lineTo(r, 0);
  5333. }
  5334. };
  5335. const shapes = new Map([
  5336. ["arrow", shapeArrow],
  5337. ["spike", shapeSpike]
  5338. ]);
  5339. function isShapeObject(value) {
  5340. return value && typeof value.draw === "function";
  5341. }
  5342. function maybeShape(shape) {
  5343. if (isShapeObject(shape)) return shape;
  5344. const value = shapes.get(`${shape}`.toLowerCase());
  5345. if (value) return value;
  5346. throw new Error(`invalid shape: ${shape}`);
  5347. }
  5348. class Vector extends Mark {
  5349. constructor(data, options = {}) {
  5350. const {x, y, r = defaultRadius, length, rotate, shape = shapeArrow, anchor = "middle", frameAnchor} = options;
  5351. const [vl, cl] = maybeNumberChannel(length, 12);
  5352. const [vr, cr] = maybeNumberChannel(rotate, 0);
  5353. super(
  5354. data,
  5355. {
  5356. x: {value: x, scale: "x", optional: true},
  5357. y: {value: y, scale: "y", optional: true},
  5358. length: {value: vl, scale: "length", optional: true},
  5359. rotate: {value: vr, optional: true}
  5360. },
  5361. options,
  5362. defaults$i
  5363. );
  5364. this.r = +r;
  5365. this.length = cl;
  5366. this.rotate = cr;
  5367. this.shape = maybeShape(shape);
  5368. this.anchor = keyword(anchor, "anchor", ["start", "middle", "end"]);
  5369. this.frameAnchor = maybeFrameAnchor(frameAnchor);
  5370. }
  5371. render(index, scales, channels, dimensions, context) {
  5372. const {x, y} = scales;
  5373. const {x: X, y: Y, length: L, rotate: A} = channels;
  5374. const {length, rotate, anchor, shape, r} = this;
  5375. const [cx, cy] = applyFrameAnchor(this, dimensions);
  5376. return create("svg:g", context)
  5377. .call(applyIndirectStyles, this, dimensions, context)
  5378. .call(applyTransform, this, {x: X && x, y: Y && y})
  5379. .call((g) =>
  5380. g
  5381. .selectAll()
  5382. .data(index)
  5383. .enter()
  5384. .append("path")
  5385. .call(applyDirectStyles, this)
  5386. .attr(
  5387. "transform",
  5388. template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${
  5389. A ? (i) => ` rotate(${A[i]})` : rotate ? ` rotate(${rotate})` : ``
  5390. }${
  5391. anchor === "start"
  5392. ? ``
  5393. : anchor === "end"
  5394. ? L
  5395. ? (i) => ` translate(0,${L[i]})`
  5396. : ` translate(0,${length})`
  5397. : L
  5398. ? (i) => ` translate(0,${L[i] / 2})`
  5399. : ` translate(0,${length / 2})`
  5400. }`
  5401. )
  5402. .attr(
  5403. "d",
  5404. L
  5405. ? (i) => {
  5406. const p = d3.pathRound();
  5407. shape.draw(p, L[i], r);
  5408. return p;
  5409. }
  5410. : (() => {
  5411. const p = d3.pathRound();
  5412. shape.draw(p, length, r);
  5413. return p;
  5414. })()
  5415. )
  5416. .call(applyChannelStyles, this, channels)
  5417. )
  5418. .node();
  5419. }
  5420. }
  5421. function vector(data, options = {}) {
  5422. let {x, y, ...rest} = options;
  5423. if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y);
  5424. return new Vector(data, {...rest, x, y});
  5425. }
  5426. function vectorX(data, options = {}) {
  5427. const {x = identity$1, ...rest} = options;
  5428. return new Vector(data, {...rest, x});
  5429. }
  5430. function vectorY(data, options = {}) {
  5431. const {y = identity$1, ...rest} = options;
  5432. return new Vector(data, {...rest, y});
  5433. }
  5434. function spike(data, options = {}) {
  5435. const {
  5436. shape = shapeSpike,
  5437. stroke = defaults$i.stroke,
  5438. strokeWidth = 1,
  5439. fill = stroke,
  5440. fillOpacity = 0.3,
  5441. anchor = "start",
  5442. ...rest
  5443. } = options;
  5444. return vector(data, {...rest, shape, stroke, strokeWidth, fill, fillOpacity, anchor});
  5445. }
  5446. function maybeData(data, options) {
  5447. if (arguments.length < 2 && !isIterable(data)) (options = data), (data = null);
  5448. if (options === undefined) options = {};
  5449. return [data, options];
  5450. }
  5451. function maybeAnchor$2({anchor} = {}, anchors) {
  5452. return anchor === undefined ? anchors[0] : keyword(anchor, "anchor", anchors);
  5453. }
  5454. function anchorY(options) {
  5455. return maybeAnchor$2(options, ["left", "right"]);
  5456. }
  5457. function anchorFy(options) {
  5458. return maybeAnchor$2(options, ["right", "left"]);
  5459. }
  5460. function anchorX(options) {
  5461. return maybeAnchor$2(options, ["bottom", "top"]);
  5462. }
  5463. function anchorFx(options) {
  5464. return maybeAnchor$2(options, ["top", "bottom"]);
  5465. }
  5466. function axisY() {
  5467. const [data, options] = maybeData(...arguments);
  5468. return axisKy("y", anchorY(options), data, options);
  5469. }
  5470. function axisFy() {
  5471. const [data, options] = maybeData(...arguments);
  5472. return axisKy("fy", anchorFy(options), data, options);
  5473. }
  5474. function axisX() {
  5475. const [data, options] = maybeData(...arguments);
  5476. return axisKx("x", anchorX(options), data, options);
  5477. }
  5478. function axisFx() {
  5479. const [data, options] = maybeData(...arguments);
  5480. return axisKx("fx", anchorFx(options), data, options);
  5481. }
  5482. function axisKy(
  5483. k,
  5484. anchor,
  5485. data,
  5486. {
  5487. color = "currentColor",
  5488. opacity = 1,
  5489. stroke = color,
  5490. strokeOpacity = opacity,
  5491. strokeWidth = 1,
  5492. fill = color,
  5493. fillOpacity = opacity,
  5494. textAnchor,
  5495. textStroke,
  5496. textStrokeOpacity,
  5497. textStrokeWidth,
  5498. tickSize = k === "y" ? 6 : 0,
  5499. tickPadding,
  5500. tickRotate,
  5501. x,
  5502. margin,
  5503. marginTop = margin === undefined ? 20 : margin,
  5504. marginRight = margin === undefined ? (anchor === "right" ? 40 : 0) : margin,
  5505. marginBottom = margin === undefined ? 20 : margin,
  5506. marginLeft = margin === undefined ? (anchor === "left" ? 40 : 0) : margin,
  5507. label,
  5508. labelAnchor,
  5509. labelArrow,
  5510. labelOffset,
  5511. ariaLabel = `${k}-axis`,
  5512. ...options
  5513. }
  5514. ) {
  5515. tickSize = number$1(tickSize);
  5516. tickPadding = number$1(tickPadding);
  5517. tickRotate = number$1(tickRotate);
  5518. if (labelAnchor !== undefined) labelAnchor = keyword(labelAnchor, "labelAnchor", ["center", "top", "bottom"]);
  5519. labelArrow = maybeLabelArrow(labelArrow);
  5520. return marks(
  5521. tickSize && !isNoneish(stroke)
  5522. ? axisTickKy(k, anchor, data, {
  5523. stroke,
  5524. strokeOpacity,
  5525. strokeWidth,
  5526. tickSize,
  5527. tickPadding,
  5528. tickRotate,
  5529. x,
  5530. ariaLabel,
  5531. ...options
  5532. })
  5533. : null,
  5534. !isNoneish(fill)
  5535. ? axisTextKy(k, anchor, data, {
  5536. fill,
  5537. fillOpacity,
  5538. stroke: textStroke,
  5539. strokeOpacity: textStrokeOpacity,
  5540. strokeWidth: textStrokeWidth,
  5541. textAnchor,
  5542. tickSize,
  5543. tickPadding,
  5544. tickRotate,
  5545. x,
  5546. marginTop,
  5547. marginRight,
  5548. marginBottom,
  5549. marginLeft,
  5550. ariaLabel,
  5551. ...options
  5552. })
  5553. : null,
  5554. !isNoneish(fill) && label !== null
  5555. ? text(
  5556. [],
  5557. labelOptions({fill, fillOpacity, ...options}, function (data, facets, channels, scales, dimensions) {
  5558. const scale = scales[k];
  5559. const {marginTop, marginRight, marginBottom, marginLeft} = (k === "y" && dimensions.inset) || dimensions;
  5560. const cla = labelAnchor ?? (scale.bandwidth ? "center" : "top");
  5561. const clo = labelOffset ?? (anchor === "right" ? marginRight : marginLeft) - 3;
  5562. if (cla === "center") {
  5563. this.textAnchor = undefined; // middle
  5564. this.lineAnchor = anchor === "right" ? "bottom" : "top";
  5565. this.frameAnchor = anchor;
  5566. this.rotate = -90;
  5567. } else {
  5568. this.textAnchor = anchor === "right" ? "end" : "start";
  5569. this.lineAnchor = cla;
  5570. this.frameAnchor = `${cla}-${anchor}`;
  5571. this.rotate = 0;
  5572. }
  5573. this.dy = cla === "top" ? 3 - marginTop : cla === "bottom" ? marginBottom - 3 : 0;
  5574. this.dx = anchor === "right" ? clo : -clo;
  5575. this.ariaLabel = `${ariaLabel} label`;
  5576. return {
  5577. facets: [[0]],
  5578. channels: {text: {value: [formatAxisLabel(k, scale, {anchor, label, labelAnchor: cla, labelArrow})]}}
  5579. };
  5580. })
  5581. )
  5582. : null
  5583. );
  5584. }
  5585. function axisKx(
  5586. k,
  5587. anchor,
  5588. data,
  5589. {
  5590. color = "currentColor",
  5591. opacity = 1,
  5592. stroke = color,
  5593. strokeOpacity = opacity,
  5594. strokeWidth = 1,
  5595. fill = color,
  5596. fillOpacity = opacity,
  5597. textAnchor,
  5598. textStroke,
  5599. textStrokeOpacity,
  5600. textStrokeWidth,
  5601. tickSize = k === "x" ? 6 : 0,
  5602. tickPadding,
  5603. tickRotate,
  5604. y,
  5605. margin,
  5606. marginTop = margin === undefined ? (anchor === "top" ? 30 : 0) : margin,
  5607. marginRight = margin === undefined ? 20 : margin,
  5608. marginBottom = margin === undefined ? (anchor === "bottom" ? 30 : 0) : margin,
  5609. marginLeft = margin === undefined ? 20 : margin,
  5610. label,
  5611. labelAnchor,
  5612. labelArrow,
  5613. labelOffset,
  5614. ariaLabel = `${k}-axis`,
  5615. ...options
  5616. }
  5617. ) {
  5618. tickSize = number$1(tickSize);
  5619. tickPadding = number$1(tickPadding);
  5620. tickRotate = number$1(tickRotate);
  5621. if (labelAnchor !== undefined) labelAnchor = keyword(labelAnchor, "labelAnchor", ["center", "left", "right"]);
  5622. labelArrow = maybeLabelArrow(labelArrow);
  5623. return marks(
  5624. tickSize && !isNoneish(stroke)
  5625. ? axisTickKx(k, anchor, data, {
  5626. stroke,
  5627. strokeOpacity,
  5628. strokeWidth,
  5629. tickSize,
  5630. tickPadding,
  5631. tickRotate,
  5632. y,
  5633. ariaLabel,
  5634. ...options
  5635. })
  5636. : null,
  5637. !isNoneish(fill)
  5638. ? axisTextKx(k, anchor, data, {
  5639. fill,
  5640. fillOpacity,
  5641. stroke: textStroke,
  5642. strokeOpacity: textStrokeOpacity,
  5643. strokeWidth: textStrokeWidth,
  5644. textAnchor,
  5645. tickSize,
  5646. tickPadding,
  5647. tickRotate,
  5648. y,
  5649. marginTop,
  5650. marginRight,
  5651. marginBottom,
  5652. marginLeft,
  5653. ariaLabel,
  5654. ...options
  5655. })
  5656. : null,
  5657. !isNoneish(fill) && label !== null
  5658. ? text(
  5659. [],
  5660. labelOptions({fill, fillOpacity, ...options}, function (data, facets, channels, scales, dimensions) {
  5661. const scale = scales[k];
  5662. const {marginTop, marginRight, marginBottom, marginLeft} = (k === "x" && dimensions.inset) || dimensions;
  5663. const cla = labelAnchor ?? (scale.bandwidth ? "center" : "right");
  5664. const clo = labelOffset ?? (anchor === "top" ? marginTop : marginBottom) - 3;
  5665. if (cla === "center") {
  5666. this.frameAnchor = anchor;
  5667. this.textAnchor = undefined; // middle
  5668. } else {
  5669. this.frameAnchor = `${anchor}-${cla}`;
  5670. this.textAnchor = cla === "right" ? "end" : "start";
  5671. }
  5672. this.lineAnchor = anchor;
  5673. this.dy = anchor === "top" ? -clo : clo;
  5674. this.dx = cla === "right" ? marginRight - 3 : cla === "left" ? 3 - marginLeft : 0;
  5675. this.ariaLabel = `${ariaLabel} label`;
  5676. return {
  5677. facets: [[0]],
  5678. channels: {text: {value: [formatAxisLabel(k, scale, {anchor, label, labelAnchor: cla, labelArrow})]}}
  5679. };
  5680. })
  5681. )
  5682. : null
  5683. );
  5684. }
  5685. function axisTickKy(
  5686. k,
  5687. anchor,
  5688. data,
  5689. {
  5690. strokeWidth = 1,
  5691. strokeLinecap = null,
  5692. strokeLinejoin = null,
  5693. facetAnchor = anchor + (k === "y" ? "-empty" : ""),
  5694. frameAnchor = anchor,
  5695. tickSize,
  5696. inset = 0,
  5697. insetLeft = inset,
  5698. insetRight = inset,
  5699. dx = 0,
  5700. y = k === "y" ? undefined : null,
  5701. ariaLabel,
  5702. ...options
  5703. }
  5704. ) {
  5705. return axisMark(
  5706. vectorY,
  5707. k,
  5708. data,
  5709. {
  5710. ariaLabel: `${ariaLabel} tick`,
  5711. ariaHidden: true
  5712. },
  5713. {
  5714. strokeWidth,
  5715. strokeLinecap,
  5716. strokeLinejoin,
  5717. facetAnchor,
  5718. frameAnchor,
  5719. y,
  5720. ...options,
  5721. dx: anchor === "left" ? +dx - offset + +insetLeft : +dx + offset - insetRight,
  5722. anchor: "start",
  5723. length: tickSize,
  5724. shape: anchor === "left" ? shapeTickLeft : shapeTickRight
  5725. }
  5726. );
  5727. }
  5728. function axisTickKx(
  5729. k,
  5730. anchor,
  5731. data,
  5732. {
  5733. strokeWidth = 1,
  5734. strokeLinecap = null,
  5735. strokeLinejoin = null,
  5736. facetAnchor = anchor + (k === "x" ? "-empty" : ""),
  5737. frameAnchor = anchor,
  5738. tickSize,
  5739. inset = 0,
  5740. insetTop = inset,
  5741. insetBottom = inset,
  5742. dy = 0,
  5743. x = k === "x" ? undefined : null,
  5744. ariaLabel,
  5745. ...options
  5746. }
  5747. ) {
  5748. return axisMark(
  5749. vectorX,
  5750. k,
  5751. data,
  5752. {
  5753. ariaLabel: `${ariaLabel} tick`,
  5754. ariaHidden: true
  5755. },
  5756. {
  5757. strokeWidth,
  5758. strokeLinejoin,
  5759. strokeLinecap,
  5760. facetAnchor,
  5761. frameAnchor,
  5762. x,
  5763. ...options,
  5764. dy: anchor === "bottom" ? +dy - offset - insetBottom : +dy + offset + +insetTop,
  5765. anchor: "start",
  5766. length: tickSize,
  5767. shape: anchor === "bottom" ? shapeTickBottom : shapeTickTop
  5768. }
  5769. );
  5770. }
  5771. function axisTextKy(
  5772. k,
  5773. anchor,
  5774. data,
  5775. {
  5776. facetAnchor = anchor + (k === "y" ? "-empty" : ""),
  5777. frameAnchor = anchor,
  5778. tickSize,
  5779. tickRotate = 0,
  5780. tickPadding = Math.max(3, 9 - tickSize) + (Math.abs(tickRotate) > 60 ? 4 * Math.cos(tickRotate * radians) : 0),
  5781. text,
  5782. textAnchor = Math.abs(tickRotate) > 60 ? "middle" : anchor === "left" ? "end" : "start",
  5783. lineAnchor = tickRotate > 60 ? "top" : tickRotate < -60 ? "bottom" : "middle",
  5784. fontVariant,
  5785. inset = 0,
  5786. insetLeft = inset,
  5787. insetRight = inset,
  5788. dx = 0,
  5789. ariaLabel,
  5790. y = k === "y" ? undefined : null,
  5791. ...options
  5792. }
  5793. ) {
  5794. return axisMark(
  5795. textY,
  5796. k,
  5797. data,
  5798. {ariaLabel: `${ariaLabel} tick label`},
  5799. {
  5800. facetAnchor,
  5801. frameAnchor,
  5802. text,
  5803. textAnchor,
  5804. lineAnchor,
  5805. fontVariant,
  5806. rotate: tickRotate,
  5807. y,
  5808. ...options,
  5809. dx: anchor === "left" ? +dx - tickSize - tickPadding + +insetLeft : +dx + +tickSize + +tickPadding - insetRight
  5810. },
  5811. function (scale, data, ticks, tickFormat, channels) {
  5812. if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale);
  5813. if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor);
  5814. }
  5815. );
  5816. }
  5817. function axisTextKx(
  5818. k,
  5819. anchor,
  5820. data,
  5821. {
  5822. facetAnchor = anchor + (k === "x" ? "-empty" : ""),
  5823. frameAnchor = anchor,
  5824. tickSize,
  5825. tickRotate = 0,
  5826. tickPadding = Math.max(3, 9 - tickSize) + (Math.abs(tickRotate) >= 10 ? 4 * Math.cos(tickRotate * radians) : 0),
  5827. text,
  5828. textAnchor = Math.abs(tickRotate) >= 10 ? ((tickRotate < 0) ^ (anchor === "bottom") ? "start" : "end") : "middle",
  5829. lineAnchor = Math.abs(tickRotate) >= 10 ? "middle" : anchor === "bottom" ? "top" : "bottom",
  5830. fontVariant,
  5831. inset = 0,
  5832. insetTop = inset,
  5833. insetBottom = inset,
  5834. dy = 0,
  5835. x = k === "x" ? undefined : null,
  5836. ariaLabel,
  5837. ...options
  5838. }
  5839. ) {
  5840. return axisMark(
  5841. textX,
  5842. k,
  5843. data,
  5844. {ariaLabel: `${ariaLabel} tick label`},
  5845. {
  5846. facetAnchor,
  5847. frameAnchor,
  5848. text: text === undefined ? null : text,
  5849. textAnchor,
  5850. lineAnchor,
  5851. fontVariant,
  5852. rotate: tickRotate,
  5853. x,
  5854. ...options,
  5855. dy: anchor === "bottom" ? +dy + +tickSize + +tickPadding - insetBottom : +dy - tickSize - tickPadding + +insetTop
  5856. },
  5857. function (scale, data, ticks, tickFormat, channels) {
  5858. if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale);
  5859. if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor);
  5860. }
  5861. );
  5862. }
  5863. function gridY() {
  5864. const [data, options] = maybeData(...arguments);
  5865. return gridKy("y", anchorY(options), data, options);
  5866. }
  5867. function gridFy() {
  5868. const [data, options] = maybeData(...arguments);
  5869. return gridKy("fy", anchorFy(options), data, options);
  5870. }
  5871. function gridX() {
  5872. const [data, options] = maybeData(...arguments);
  5873. return gridKx("x", anchorX(options), data, options);
  5874. }
  5875. function gridFx() {
  5876. const [data, options] = maybeData(...arguments);
  5877. return gridKx("fx", anchorFx(options), data, options);
  5878. }
  5879. function gridKy(
  5880. k,
  5881. anchor,
  5882. data,
  5883. {
  5884. y = k === "y" ? undefined : null,
  5885. x = null,
  5886. x1 = anchor === "left" ? x : null,
  5887. x2 = anchor === "right" ? x : null,
  5888. ariaLabel = `${k}-grid`,
  5889. ariaHidden = true,
  5890. ...options
  5891. }
  5892. ) {
  5893. return axisMark(ruleY, k, data, {ariaLabel, ariaHidden}, {y, x1, x2, ...gridDefaults(options)});
  5894. }
  5895. function gridKx(
  5896. k,
  5897. anchor,
  5898. data,
  5899. {
  5900. x = k === "x" ? undefined : null,
  5901. y = null,
  5902. y1 = anchor === "top" ? y : null,
  5903. y2 = anchor === "bottom" ? y : null,
  5904. ariaLabel = `${k}-grid`,
  5905. ariaHidden = true,
  5906. ...options
  5907. }
  5908. ) {
  5909. return axisMark(ruleX, k, data, {ariaLabel, ariaHidden}, {x, y1, y2, ...gridDefaults(options)});
  5910. }
  5911. function gridDefaults({
  5912. color = "currentColor",
  5913. opacity = 0.1,
  5914. stroke = color,
  5915. strokeOpacity = opacity,
  5916. strokeWidth = 1,
  5917. ...options
  5918. }) {
  5919. return {stroke, strokeOpacity, strokeWidth, ...options};
  5920. }
  5921. function labelOptions(
  5922. {
  5923. fill,
  5924. fillOpacity,
  5925. fontFamily,
  5926. fontSize,
  5927. fontStyle,
  5928. fontVariant,
  5929. fontWeight,
  5930. monospace,
  5931. pointerEvents,
  5932. shapeRendering,
  5933. clip = false
  5934. },
  5935. initializer
  5936. ) {
  5937. // Only propagate these options if constant.
  5938. [, fill] = maybeColorChannel(fill);
  5939. [, fillOpacity] = maybeNumberChannel(fillOpacity);
  5940. return {
  5941. facet: "super",
  5942. x: null,
  5943. y: null,
  5944. fill,
  5945. fillOpacity,
  5946. fontFamily,
  5947. fontSize,
  5948. fontStyle,
  5949. fontVariant,
  5950. fontWeight,
  5951. monospace,
  5952. pointerEvents,
  5953. shapeRendering,
  5954. clip,
  5955. initializer
  5956. };
  5957. }
  5958. function axisMark(mark, k, data, properties, options, initialize) {
  5959. let channels;
  5960. function axisInitializer(data, facets, _channels, scales, dimensions, context) {
  5961. const initializeFacets = data == null && (k === "fx" || k === "fy");
  5962. const {[k]: scale} = scales;
  5963. if (!scale) throw new Error(`missing scale: ${k}`);
  5964. const domain = scale.domain();
  5965. let {interval, ticks, tickFormat, tickSpacing = k === "x" ? 80 : 35} = options;
  5966. // For a scale with a temporal domain, also allow the ticks to be specified
  5967. // as a string which is promoted to a time interval. In the case of ordinal
  5968. // scales, the interval is interpreted as UTC.
  5969. if (typeof ticks === "string" && hasTemporalDomain(scale)) (interval = ticks), (ticks = undefined);
  5970. // The interval axis option is an alternative method of specifying ticks;
  5971. // for example, for a numeric scale, ticks = 5 means “about 5 ticks” whereas
  5972. // interval = 5 means “ticks every 5 units”. (This is not to be confused
  5973. // with the interval scale option, which affects the scale’s behavior!)
  5974. // Lastly use the tickSpacing option to infer the desired tick count.
  5975. if (ticks === undefined) ticks = maybeRangeInterval(interval, scale.type) ?? inferTickCount(scale, tickSpacing);
  5976. if (data == null) {
  5977. if (isIterable(ticks)) {
  5978. // Use explicit ticks, if specified.
  5979. data = arrayify(ticks);
  5980. } else if (isInterval(ticks)) {
  5981. // Use the tick interval, if specified.
  5982. data = inclusiveRange(ticks, ...d3.extent(domain));
  5983. } else if (scale.interval) {
  5984. // If the scale interval is a standard time interval such as "day", we
  5985. // may be able to generalize the scale interval it to a larger aligned
  5986. // time interval to create the desired number of ticks.
  5987. let interval = scale.interval;
  5988. if (scale.ticks) {
  5989. const [min, max] = d3.extent(domain);
  5990. const n = (max - min) / interval[intervalDuration]; // current tick count
  5991. // We don’t explicitly check that given interval is a time interval;
  5992. // in that case the generalized interval will be undefined, just like
  5993. // a nonstandard interval. TODO Generalize integer intervals, too.
  5994. interval = generalizeTimeInterval(interval, n / ticks) ?? interval;
  5995. data = inclusiveRange(interval, min, max);
  5996. } else {
  5997. data = domain;
  5998. const n = data.length; // current tick count
  5999. interval = generalizeTimeInterval(interval, n / ticks) ?? interval;
  6000. if (interval !== scale.interval) data = inclusiveRange(interval, ...d3.extent(data));
  6001. }
  6002. if (interval === scale.interval) {
  6003. // If we weren’t able to generalize the scale’s interval, compute the
  6004. // positive number n such that taking every nth value from the scale’s
  6005. // domain produces as close as possible to the desired number of
  6006. // ticks. For example, if the domain has 100 values and 5 ticks are
  6007. // desired, n = 20.
  6008. const n = Math.round(data.length / ticks);
  6009. if (n > 1) data = data.filter((d, i) => i % n === 0);
  6010. }
  6011. } else if (scale.ticks) {
  6012. data = scale.ticks(ticks);
  6013. } else {
  6014. // For ordinal scales, the domain will already be generated using the
  6015. // scale’s interval, if any.
  6016. data = domain;
  6017. }
  6018. if (!scale.ticks && data.length && data !== domain) {
  6019. // For ordinal scales, intersect the ticks with the scale domain since
  6020. // the scale is only defined on its domain. If all of the ticks are
  6021. // removed, then warn that the ticks and scale domain may be misaligned
  6022. // (e.g., "year" ticks and "4 weeks" interval).
  6023. const domainSet = new d3.InternSet(domain);
  6024. data = data.filter((d) => domainSet.has(d));
  6025. if (!data.length) warn(`Warning: the ${k}-axis ticks appear to not align with the scale domain, resulting in no ticks. Try different ticks?`); // prettier-ignore
  6026. }
  6027. if (k === "y" || k === "x") {
  6028. facets = [range(data)];
  6029. } else {
  6030. channels[k] = {scale: k, value: identity$1};
  6031. }
  6032. }
  6033. initialize?.call(this, scale, data, ticks, tickFormat, channels);
  6034. const initializedChannels = Object.fromEntries(
  6035. Object.entries(channels).map(([name, channel]) => {
  6036. return [name, {...channel, value: valueof(data, channel.value)}];
  6037. })
  6038. );
  6039. if (initializeFacets) facets = context.filterFacets(data, initializedChannels);
  6040. return {data, facets, channels: initializedChannels};
  6041. }
  6042. // Apply any basic initializers after the axis initializer computes the ticks.
  6043. const basicInitializer = initializer(options).initializer;
  6044. const m = mark(data, initializer({...options, initializer: axisInitializer}, basicInitializer));
  6045. if (data == null) {
  6046. channels = m.channels;
  6047. m.channels = {};
  6048. } else {
  6049. channels = {};
  6050. }
  6051. if (properties !== undefined) Object.assign(m, properties);
  6052. if (m.clip === undefined) m.clip = false; // don’t clip axes by default
  6053. return m;
  6054. }
  6055. function inferTickCount(scale, tickSpacing) {
  6056. const [min, max] = d3.extent(scale.range());
  6057. return (max - min) / tickSpacing;
  6058. }
  6059. function inferTextChannel(scale, data, ticks, tickFormat, anchor) {
  6060. return {value: inferTickFormat(scale, data, ticks, tickFormat, anchor)};
  6061. }
  6062. // D3’s ordinal scales simply use toString by default, but if the ordinal scale
  6063. // domain (or ticks) are numbers or dates (say because we’re applying a time
  6064. // interval to the ordinal scale), we want Plot’s default formatter. And for
  6065. // time ticks, we want to use the multi-line time format (e.g., Jan 26) if
  6066. // possible, or the default ISO format (2014-01-26). TODO We need a better way
  6067. // to infer whether the ordinal scale is UTC or local time.
  6068. function inferTickFormat(scale, data, ticks, tickFormat, anchor) {
  6069. return typeof tickFormat === "function" && !(scale.type === "log" && scale.tickFormat)
  6070. ? tickFormat
  6071. : tickFormat === undefined && data && isTemporal(data)
  6072. ? inferTimeFormat(scale.type, data, anchor) ?? formatDefault
  6073. : scale.tickFormat
  6074. ? scale.tickFormat(typeof ticks === "number" ? ticks : null, tickFormat)
  6075. : typeof tickFormat === "string" && scale.domain().length > 0
  6076. ? (isTemporal(scale.domain()) ? d3.utcFormat : d3.format)(tickFormat)
  6077. : tickFormat === undefined
  6078. ? formatDefault
  6079. : constant(tickFormat);
  6080. }
  6081. function inclusiveRange(interval, min, max) {
  6082. return interval.range(min, interval.offset(interval.floor(max)));
  6083. }
  6084. const shapeTickBottom = {
  6085. draw(context, l) {
  6086. context.moveTo(0, 0);
  6087. context.lineTo(0, l);
  6088. }
  6089. };
  6090. const shapeTickTop = {
  6091. draw(context, l) {
  6092. context.moveTo(0, 0);
  6093. context.lineTo(0, -l);
  6094. }
  6095. };
  6096. const shapeTickLeft = {
  6097. draw(context, l) {
  6098. context.moveTo(0, 0);
  6099. context.lineTo(-l, 0);
  6100. }
  6101. };
  6102. const shapeTickRight = {
  6103. draw(context, l) {
  6104. context.moveTo(0, 0);
  6105. context.lineTo(l, 0);
  6106. }
  6107. };
  6108. // TODO Unify this with the other inferFontVariant; here we only have a scale
  6109. // function rather than a scale descriptor.
  6110. function inferFontVariant(scale) {
  6111. return scale.bandwidth && !scale.interval ? undefined : "tabular-nums";
  6112. }
  6113. // Takes the scale label, and if this is not an ordinal scale and the label was
  6114. // inferred from an associated channel, adds an orientation-appropriate arrow.
  6115. function formatAxisLabel(k, scale, {anchor, label = scale.label, labelAnchor, labelArrow} = {}) {
  6116. if (label == null || (label.inferred && hasTemporalDomain(scale) && /^(date|time|year)$/i.test(label))) return;
  6117. label = String(label); // coerce to a string after checking if inferred
  6118. if (labelArrow === "auto") labelArrow = (!scale.bandwidth || scale.interval) && !/[↑↓→←]/.test(label);
  6119. if (!labelArrow) return label;
  6120. if (labelArrow === true) {
  6121. const order = inferScaleOrder(scale);
  6122. if (order)
  6123. labelArrow =
  6124. /x$/.test(k) || labelAnchor === "center"
  6125. ? /x$/.test(k) === order < 0
  6126. ? "left"
  6127. : "right"
  6128. : order < 0
  6129. ? "up"
  6130. : "down";
  6131. }
  6132. switch (labelArrow) {
  6133. case "left":
  6134. return `← ${label}`;
  6135. case "right":
  6136. return `${label} →`;
  6137. case "up":
  6138. return anchor === "right" ? `${label} ↑` : `↑ ${label}`;
  6139. case "down":
  6140. return anchor === "right" ? `${label} ↓` : `↓ ${label}`;
  6141. }
  6142. return label;
  6143. }
  6144. function maybeLabelArrow(labelArrow = "auto") {
  6145. return isNoneish(labelArrow)
  6146. ? false
  6147. : typeof labelArrow === "boolean"
  6148. ? labelArrow
  6149. : keyword(labelArrow, "labelArrow", ["auto", "up", "right", "down", "left"]);
  6150. }
  6151. function hasTemporalDomain(scale) {
  6152. return isTemporal(scale.domain());
  6153. }
  6154. function maybeScale(scale, key) {
  6155. if (key == null) return key;
  6156. const s = scale(key);
  6157. if (!s) throw new Error(`scale not found: ${key}`);
  6158. return s;
  6159. }
  6160. function legendSwatches(color, {opacity, ...options} = {}) {
  6161. if (!isOrdinalScale(color) && !isThresholdScale(color))
  6162. throw new Error(`swatches legend requires ordinal or threshold color scale (not ${color.type})`);
  6163. return legendItems(color, options, (selection, scale, width, height) =>
  6164. selection
  6165. .append("svg")
  6166. .attr("width", width)
  6167. .attr("height", height)
  6168. .attr("fill", scale.scale)
  6169. .attr("fill-opacity", maybeNumberChannel(opacity)[1])
  6170. .append("rect")
  6171. .attr("width", "100%")
  6172. .attr("height", "100%")
  6173. );
  6174. }
  6175. function legendSymbols(
  6176. symbol,
  6177. {
  6178. fill = symbol.hint?.fill !== undefined ? symbol.hint.fill : "none",
  6179. fillOpacity = 1,
  6180. stroke = symbol.hint?.stroke !== undefined ? symbol.hint.stroke : isNoneish(fill) ? "currentColor" : "none",
  6181. strokeOpacity = 1,
  6182. strokeWidth = 1.5,
  6183. r = 4.5,
  6184. ...options
  6185. } = {},
  6186. scale
  6187. ) {
  6188. const [vf, cf] = maybeColorChannel(fill);
  6189. const [vs, cs] = maybeColorChannel(stroke);
  6190. const sf = maybeScale(scale, vf);
  6191. const ss = maybeScale(scale, vs);
  6192. const size = r * r * Math.PI;
  6193. fillOpacity = maybeNumberChannel(fillOpacity)[1];
  6194. strokeOpacity = maybeNumberChannel(strokeOpacity)[1];
  6195. strokeWidth = maybeNumberChannel(strokeWidth)[1];
  6196. return legendItems(symbol, options, (selection, scale, width, height) =>
  6197. selection
  6198. .append("svg")
  6199. .attr("viewBox", "-8 -8 16 16")
  6200. .attr("width", width)
  6201. .attr("height", height)
  6202. .attr("fill", vf === "color" ? (d) => sf.scale(d) : cf)
  6203. .attr("fill-opacity", fillOpacity)
  6204. .attr("stroke", vs === "color" ? (d) => ss.scale(d) : cs)
  6205. .attr("stroke-opacity", strokeOpacity)
  6206. .attr("stroke-width", strokeWidth)
  6207. .append("path")
  6208. .attr("d", (d) => {
  6209. const p = d3.pathRound();
  6210. symbol.scale(d).draw(p, size);
  6211. return p;
  6212. })
  6213. );
  6214. }
  6215. function legendItems(scale, options = {}, swatch) {
  6216. let {
  6217. columns,
  6218. tickFormat,
  6219. fontVariant = inferFontVariant$2(scale),
  6220. // TODO label,
  6221. swatchSize = 15,
  6222. swatchWidth = swatchSize,
  6223. swatchHeight = swatchSize,
  6224. marginLeft = 0,
  6225. className,
  6226. style,
  6227. width
  6228. } = options;
  6229. const context = createContext(options);
  6230. className = maybeClassName(className);
  6231. tickFormat = inferTickFormat(scale.scale, scale.domain, undefined, tickFormat);
  6232. const swatches = create("div", context).attr(
  6233. "class",
  6234. `${className}-swatches ${className}-swatches-${columns != null ? "columns" : "wrap"}`
  6235. );
  6236. let extraStyle;
  6237. if (columns != null) {
  6238. extraStyle = `:where(.${className}-swatches-columns .${className}-swatch) {
  6239. display: flex;
  6240. align-items: center;
  6241. break-inside: avoid;
  6242. padding-bottom: 1px;
  6243. }
  6244. :where(.${className}-swatches-columns .${className}-swatch::before) {
  6245. flex-shrink: 0;
  6246. }
  6247. :where(.${className}-swatches-columns .${className}-swatch-label) {
  6248. white-space: nowrap;
  6249. overflow: hidden;
  6250. text-overflow: ellipsis;
  6251. }`;
  6252. swatches
  6253. .style("columns", columns)
  6254. .selectAll()
  6255. .data(scale.domain)
  6256. .enter()
  6257. .append("div")
  6258. .attr("class", `${className}-swatch`)
  6259. .call(swatch, scale, swatchWidth, swatchHeight)
  6260. .call((item) =>
  6261. item.append("div").attr("class", `${className}-swatch-label`).attr("title", tickFormat).text(tickFormat)
  6262. );
  6263. } else {
  6264. extraStyle = `:where(.${className}-swatches-wrap) {
  6265. display: flex;
  6266. align-items: center;
  6267. min-height: 33px;
  6268. flex-wrap: wrap;
  6269. }
  6270. :where(.${className}-swatches-wrap .${className}-swatch) {
  6271. display: inline-flex;
  6272. align-items: center;
  6273. margin-right: 1em;
  6274. }`;
  6275. swatches
  6276. .selectAll()
  6277. .data(scale.domain)
  6278. .enter()
  6279. .append("span")
  6280. .attr("class", `${className}-swatch`)
  6281. .call(swatch, scale, swatchWidth, swatchHeight)
  6282. .append(function () {
  6283. return this.ownerDocument.createTextNode(tickFormat.apply(this, arguments));
  6284. });
  6285. }
  6286. return swatches
  6287. .call((div) =>
  6288. div.insert("style", "*").text(
  6289. `:where(.${className}-swatches) {
  6290. font-family: system-ui, sans-serif;
  6291. font-size: 10px;
  6292. margin-bottom: 0.5em;
  6293. }
  6294. :where(.${className}-swatch > svg) {
  6295. margin-right: 0.5em;
  6296. overflow: visible;
  6297. }
  6298. ${extraStyle}`
  6299. )
  6300. )
  6301. .style("margin-left", marginLeft ? `${+marginLeft}px` : null)
  6302. .style("width", width === undefined ? null : `${+width}px`)
  6303. .style("font-variant", impliedString(fontVariant, "normal"))
  6304. .call(applyInlineStyles, style)
  6305. .node();
  6306. }
  6307. const legendRegistry = new Map([
  6308. ["symbol", legendSymbols],
  6309. ["color", legendColor],
  6310. ["opacity", legendOpacity]
  6311. ]);
  6312. function legend(options = {}) {
  6313. for (const [key, value] of legendRegistry) {
  6314. const scale = options[key];
  6315. if (isScaleOptions(scale)) {
  6316. // e.g., ignore {color: "red"}
  6317. const context = createContext(options);
  6318. let hint;
  6319. // For symbol legends, pass a hint to the symbol scale.
  6320. if (key === "symbol") {
  6321. const {fill, stroke = fill === undefined && isScaleOptions(options.color) ? "color" : undefined} = options;
  6322. hint = {fill, stroke};
  6323. }
  6324. return value(normalizeScale(key, scale, hint), legendOptions(context, scale, options), (key) =>
  6325. isScaleOptions(options[key]) ? normalizeScale(key, options[key]) : null
  6326. );
  6327. }
  6328. }
  6329. throw new Error("unknown legend type; no scale found");
  6330. }
  6331. function exposeLegends(scales, context, defaults = {}) {
  6332. return (key, options) => {
  6333. if (!legendRegistry.has(key)) throw new Error(`unknown legend type: ${key}`);
  6334. if (!(key in scales)) return;
  6335. return legendRegistry.get(key)(scales[key], legendOptions(context, defaults[key], options), (key) => scales[key]);
  6336. };
  6337. }
  6338. function legendOptions({className, ...context}, {label, ticks, tickFormat} = {}, options) {
  6339. return inherit(options, {className, ...context}, {label, ticks, tickFormat});
  6340. }
  6341. function legendColor(color, {legend = true, ...options}) {
  6342. if (legend === true) legend = color.type === "ordinal" ? "swatches" : "ramp";
  6343. if (color.domain === undefined) return; // no identity legend
  6344. switch (`${legend}`.toLowerCase()) {
  6345. case "swatches":
  6346. return legendSwatches(color, options);
  6347. case "ramp":
  6348. return legendRamp(color, options);
  6349. default:
  6350. throw new Error(`unknown legend type: ${legend}`);
  6351. }
  6352. }
  6353. function legendOpacity({type, interpolate, ...scale}, {legend = true, color = d3.rgb(0, 0, 0), ...options}) {
  6354. if (!interpolate) throw new Error(`${type} opacity scales are not supported`);
  6355. if (legend === true) legend = "ramp";
  6356. if (`${legend}`.toLowerCase() !== "ramp") throw new Error(`${legend} opacity legends are not supported`);
  6357. return legendColor({type, ...scale, interpolate: interpolateOpacity(color)}, {legend, ...options});
  6358. }
  6359. function interpolateOpacity(color) {
  6360. const {r, g, b} = d3.rgb(color) || d3.rgb(0, 0, 0); // treat invalid color as black
  6361. return (t) => `rgba(${r},${g},${b},${t})`;
  6362. }
  6363. function createLegends(scales, context, options) {
  6364. const legends = [];
  6365. for (const [key, value] of legendRegistry) {
  6366. const o = options[key];
  6367. if (o?.legend && key in scales) {
  6368. const legend = value(scales[key], legendOptions(context, scales[key], o), (key) => scales[key]);
  6369. if (legend != null) legends.push(legend);
  6370. }
  6371. }
  6372. return legends;
  6373. }
  6374. function maybeIdentityX(options = {}, k = "x") {
  6375. return hasX(options) ? options : {...options, [k]: identity$1};
  6376. }
  6377. function maybeIdentityY(options = {}, k = "y") {
  6378. return hasY(options) ? options : {...options, [k]: identity$1};
  6379. }
  6380. function exclusiveFacets(data, facets) {
  6381. if (facets.length === 1) return {data, facets}; // only one facet; trivially exclusive
  6382. const n = lengthof(data);
  6383. const O = new Uint8Array(n);
  6384. let overlaps = 0;
  6385. // Count the number of overlapping indexes across facets.
  6386. for (const facet of facets) {
  6387. for (const i of facet) {
  6388. if (O[i]) ++overlaps;
  6389. O[i] = 1;
  6390. }
  6391. }
  6392. // Do nothing if the facets are already exclusive.
  6393. if (overlaps === 0) return {data, facets}; // facets are exclusive
  6394. // For each overlapping index (duplicate), assign a new unique index at the
  6395. // end of the existing array, duplicating the datum. For example, [[0, 1, 2],
  6396. // [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]]. Also attach a reindex to
  6397. // the data to preserve the association of channel values specified as arrays.
  6398. data = slice(data);
  6399. const R = (data[reindex] = new Uint32Array(n + overlaps));
  6400. facets = facets.map((facet) => slice(facet, Uint32Array));
  6401. let j = n;
  6402. O.fill(0);
  6403. for (const facet of facets) {
  6404. for (let k = 0, m = facet.length; k < m; ++k) {
  6405. const i = facet[k];
  6406. if (O[i]) (facet[k] = j), (data[j] = data[i]), (R[j] = i), ++j;
  6407. else R[i] = i;
  6408. O[i] = 1;
  6409. }
  6410. }
  6411. return {data, facets};
  6412. }
  6413. function stackX(stackOptions = {}, options = {}) {
  6414. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6415. const {y1, y = y1, x, ...rest} = options; // note: consumes x!
  6416. const [transform, Y, x1, x2] = stack(y, x, "y", "x", stackOptions, rest);
  6417. return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2)};
  6418. }
  6419. function stackX1(stackOptions = {}, options = {}) {
  6420. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6421. const {y1, y = y1, x} = options;
  6422. const [transform, Y, X] = stack(y, x, "y", "x", stackOptions, options);
  6423. return {...transform, y1, y: Y, x: X};
  6424. }
  6425. function stackX2(stackOptions = {}, options = {}) {
  6426. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6427. const {y1, y = y1, x} = options;
  6428. const [transform, Y, , X] = stack(y, x, "y", "x", stackOptions, options);
  6429. return {...transform, y1, y: Y, x: X};
  6430. }
  6431. function stackY(stackOptions = {}, options = {}) {
  6432. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6433. const {x1, x = x1, y, ...rest} = options; // note: consumes y!
  6434. const [transform, X, y1, y2] = stack(x, y, "x", "y", stackOptions, rest);
  6435. return {...transform, x1, x: X, y1, y2, y: mid(y1, y2)};
  6436. }
  6437. function stackY1(stackOptions = {}, options = {}) {
  6438. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6439. const {x1, x = x1, y} = options;
  6440. const [transform, X, Y] = stack(x, y, "x", "y", stackOptions, options);
  6441. return {...transform, x1, x: X, y: Y};
  6442. }
  6443. function stackY2(stackOptions = {}, options = {}) {
  6444. if (arguments.length === 1) [stackOptions, options] = mergeOptions$2(stackOptions);
  6445. const {x1, x = x1, y} = options;
  6446. const [transform, X, , Y] = stack(x, y, "x", "y", stackOptions, options);
  6447. return {...transform, x1, x: X, y: Y};
  6448. }
  6449. function maybeStackX({x, x1, x2, ...options} = {}) {
  6450. options = withTip(options, "y");
  6451. if (x1 === undefined && x2 === undefined) return stackX({x, ...options});
  6452. [x1, x2] = maybeZero(x, x1, x2);
  6453. return {...options, x1, x2};
  6454. }
  6455. function maybeStackY({y, y1, y2, ...options} = {}) {
  6456. options = withTip(options, "x");
  6457. if (y1 === undefined && y2 === undefined) return stackY({y, ...options});
  6458. [y1, y2] = maybeZero(y, y1, y2);
  6459. return {...options, y1, y2};
  6460. }
  6461. // The reverse option is ambiguous: it is both a stack option and a basic
  6462. // transform. If only one options object is specified, we interpret it as a
  6463. // stack option, and therefore must remove it from the propagated options.
  6464. function mergeOptions$2(options) {
  6465. const {offset, order, reverse, ...rest} = options;
  6466. return [{offset, order, reverse}, rest];
  6467. }
  6468. // This is a hint to the tooltip mark that the y1 and y2 channels (for stackY,
  6469. // or conversely x1 and x2 for stackX) represent a stacked length, and that the
  6470. // tooltip should therefore show y2-y1 instead of an extent.
  6471. const lengthy = {length: true};
  6472. function stack(x, y = one, kx, ky, {offset, order, reverse}, options) {
  6473. if (y === null) throw new Error(`stack requires ${ky}`);
  6474. const z = maybeZ(options);
  6475. const [X, setX] = maybeColumn(x);
  6476. const [Y1, setY1] = column(y);
  6477. const [Y2, setY2] = column(y);
  6478. Y1.hint = Y2.hint = lengthy;
  6479. offset = maybeOffset(offset);
  6480. order = maybeOrder(order, offset, ky);
  6481. return [
  6482. basic(options, (data, facets, plotOptions) => {
  6483. ({data, facets} = exclusiveFacets(data, facets));
  6484. const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx]));
  6485. const Y = valueof(data, y, Float64Array);
  6486. const Z = valueof(data, z);
  6487. const compare = order && order(data, X, Y, Z);
  6488. const n = lengthof(data);
  6489. const Y1 = setY1(new Float64Array(n));
  6490. const Y2 = setY2(new Float64Array(n));
  6491. const facetstacks = [];
  6492. for (const facet of facets) {
  6493. const stacks = X ? Array.from(d3.group(facet, (i) => X[i]).values()) : [facet];
  6494. if (compare) for (const stack of stacks) stack.sort(compare);
  6495. for (const stack of stacks) {
  6496. let yn = 0;
  6497. let yp = 0;
  6498. if (reverse) stack.reverse();
  6499. for (const i of stack) {
  6500. const y = Y[i];
  6501. if (y < 0) yn = Y2[i] = (Y1[i] = yn) + y;
  6502. else if (y > 0) yp = Y2[i] = (Y1[i] = yp) + y;
  6503. else Y2[i] = Y1[i] = yp; // NaN or zero
  6504. }
  6505. }
  6506. facetstacks.push(stacks);
  6507. }
  6508. if (offset) offset(facetstacks, Y1, Y2, Z);
  6509. return {data, facets};
  6510. }),
  6511. X,
  6512. Y1,
  6513. Y2
  6514. ];
  6515. }
  6516. function maybeOffset(offset) {
  6517. if (offset == null) return;
  6518. if (typeof offset === "function") return offset;
  6519. switch (`${offset}`.toLowerCase()) {
  6520. case "expand":
  6521. case "normalize":
  6522. return offsetExpand;
  6523. case "center":
  6524. case "silhouette":
  6525. return offsetCenter;
  6526. case "wiggle":
  6527. return offsetWiggle;
  6528. }
  6529. throw new Error(`unknown offset: ${offset}`);
  6530. }
  6531. // Given a single stack, returns the minimum and maximum values from the given
  6532. // Y2 column. Note that this relies on Y2 always being the outer column for
  6533. // diverging values.
  6534. function extent(stack, Y2) {
  6535. let min = 0,
  6536. max = 0;
  6537. for (const i of stack) {
  6538. const y = Y2[i];
  6539. if (y < min) min = y;
  6540. if (y > max) max = y;
  6541. }
  6542. return [min, max];
  6543. }
  6544. function offsetExpand(facetstacks, Y1, Y2) {
  6545. for (const stacks of facetstacks) {
  6546. for (const stack of stacks) {
  6547. const [yn, yp] = extent(stack, Y2);
  6548. for (const i of stack) {
  6549. const m = 1 / (yp - yn || 1);
  6550. Y1[i] = m * (Y1[i] - yn);
  6551. Y2[i] = m * (Y2[i] - yn);
  6552. }
  6553. }
  6554. }
  6555. }
  6556. function offsetCenter(facetstacks, Y1, Y2) {
  6557. for (const stacks of facetstacks) {
  6558. for (const stack of stacks) {
  6559. const [yn, yp] = extent(stack, Y2);
  6560. for (const i of stack) {
  6561. const m = (yp + yn) / 2;
  6562. Y1[i] -= m;
  6563. Y2[i] -= m;
  6564. }
  6565. }
  6566. offsetZero(stacks, Y1, Y2);
  6567. }
  6568. offsetCenterFacets(facetstacks, Y1, Y2);
  6569. }
  6570. function offsetWiggle(facetstacks, Y1, Y2, Z) {
  6571. for (const stacks of facetstacks) {
  6572. const prev = new d3.InternMap();
  6573. let y = 0;
  6574. for (const stack of stacks) {
  6575. let j = -1;
  6576. const Fi = stack.map((i) => Math.abs(Y2[i] - Y1[i]));
  6577. const Df = stack.map((i) => {
  6578. j = Z ? Z[i] : ++j;
  6579. const value = Y2[i] - Y1[i];
  6580. const diff = prev.has(j) ? value - prev.get(j) : 0;
  6581. prev.set(j, value);
  6582. return diff;
  6583. });
  6584. const Cf1 = [0, ...d3.cumsum(Df)];
  6585. for (const i of stack) {
  6586. Y1[i] += y;
  6587. Y2[i] += y;
  6588. }
  6589. const s1 = d3.sum(Fi);
  6590. if (s1) y -= d3.sum(Fi, (d, i) => (Df[i] / 2 + Cf1[i]) * d) / s1;
  6591. }
  6592. offsetZero(stacks, Y1, Y2);
  6593. }
  6594. offsetCenterFacets(facetstacks, Y1, Y2);
  6595. }
  6596. function offsetZero(stacks, Y1, Y2) {
  6597. const m = d3.min(stacks, (stack) => d3.min(stack, (i) => Y1[i]));
  6598. for (const stack of stacks) {
  6599. for (const i of stack) {
  6600. Y1[i] -= m;
  6601. Y2[i] -= m;
  6602. }
  6603. }
  6604. }
  6605. function offsetCenterFacets(facetstacks, Y1, Y2) {
  6606. const n = facetstacks.length;
  6607. if (n === 1) return;
  6608. const facets = facetstacks.map((stacks) => stacks.flat());
  6609. const m = facets.map((I) => (d3.min(I, (i) => Y1[i]) + d3.max(I, (i) => Y2[i])) / 2);
  6610. const m0 = d3.min(m);
  6611. for (let j = 0; j < n; j++) {
  6612. const p = m0 - m[j];
  6613. for (const i of facets[j]) {
  6614. Y1[i] += p;
  6615. Y2[i] += p;
  6616. }
  6617. }
  6618. }
  6619. function maybeOrder(order, offset, ky) {
  6620. if (order === undefined && offset === offsetWiggle) return orderInsideOut(ascendingDefined);
  6621. if (order == null) return;
  6622. if (typeof order === "string") {
  6623. const negate = order.startsWith("-");
  6624. const compare = negate ? descendingDefined : ascendingDefined;
  6625. switch ((negate ? order.slice(1) : order).toLowerCase()) {
  6626. case "value":
  6627. case ky:
  6628. return orderY(compare);
  6629. case "z":
  6630. return orderZ(compare);
  6631. case "sum":
  6632. return orderSum(compare);
  6633. case "appearance":
  6634. return orderAppearance(compare);
  6635. case "inside-out":
  6636. return orderInsideOut(compare);
  6637. }
  6638. return orderAccessor(field(order));
  6639. }
  6640. if (typeof order === "function") return (order.length === 1 ? orderAccessor : orderComparator)(order);
  6641. if (isArray(order)) return orderGiven(order);
  6642. throw new Error(`invalid order: ${order}`);
  6643. }
  6644. // by value
  6645. function orderY(compare) {
  6646. return (data, X, Y) => (i, j) => compare(Y[i], Y[j]);
  6647. }
  6648. // by location
  6649. function orderZ(compare) {
  6650. return (data, X, Y, Z) => (i, j) => compare(Z[i], Z[j]);
  6651. }
  6652. // by sum of value (a.k.a. “ascending”)
  6653. function orderSum(compare) {
  6654. return orderZDomain(compare, (data, X, Y, Z) =>
  6655. d3.groupSort(
  6656. range(data),
  6657. (I) => d3.sum(I, (i) => Y[i]),
  6658. (i) => Z[i]
  6659. )
  6660. );
  6661. }
  6662. // by x = argmax of value
  6663. function orderAppearance(compare) {
  6664. return orderZDomain(compare, (data, X, Y, Z) =>
  6665. d3.groupSort(
  6666. range(data),
  6667. (I) => X[d3.greatest(I, (i) => Y[i])],
  6668. (i) => Z[i]
  6669. )
  6670. );
  6671. }
  6672. // by x = argmax of value, but rearranged inside-out by alternating series
  6673. // according to the sign of a running divergence of sums
  6674. function orderInsideOut(compare) {
  6675. return orderZDomain(compare, (data, X, Y, Z) => {
  6676. const I = range(data);
  6677. const K = d3.groupSort(
  6678. I,
  6679. (I) => X[d3.greatest(I, (i) => Y[i])],
  6680. (i) => Z[i]
  6681. );
  6682. const sums = d3.rollup(
  6683. I,
  6684. (I) => d3.sum(I, (i) => Y[i]),
  6685. (i) => Z[i]
  6686. );
  6687. const Kp = [],
  6688. Kn = [];
  6689. let s = 0;
  6690. for (const k of K) {
  6691. if (s < 0) {
  6692. s += sums.get(k);
  6693. Kp.push(k);
  6694. } else {
  6695. s -= sums.get(k);
  6696. Kn.push(k);
  6697. }
  6698. }
  6699. return Kn.reverse().concat(Kp);
  6700. });
  6701. }
  6702. function orderAccessor(f) {
  6703. return (data) => {
  6704. const O = valueof(data, f);
  6705. return (i, j) => ascendingDefined(O[i], O[j]);
  6706. };
  6707. }
  6708. function orderComparator(f) {
  6709. return (data) => {
  6710. return isArray(data) ? (i, j) => f(data[i], data[j]) : (i, j) => f(data.get(i), data.get(j));
  6711. };
  6712. }
  6713. function orderGiven(domain) {
  6714. return orderZDomain(ascendingDefined, () => domain);
  6715. }
  6716. // Given an ordering (domain) of distinct values in z that can be derived from
  6717. // the data, returns a comparator that can be used to sort stacks. Note that
  6718. // this is a series order: it will be consistent across stacks.
  6719. function orderZDomain(compare, domain) {
  6720. return (data, X, Y, Z) => {
  6721. if (!Z) throw new Error("missing channel: z");
  6722. const map = new d3.InternMap(domain(data, X, Y, Z).map((d, i) => [d, i]));
  6723. return (i, j) => compare(map.get(Z[i]), map.get(Z[j]));
  6724. };
  6725. }
  6726. const defaults$h = {
  6727. ariaLabel: "rect"
  6728. };
  6729. class Rect extends Mark {
  6730. constructor(data, options = {}) {
  6731. const {x1, y1, x2, y2} = options;
  6732. super(
  6733. data,
  6734. {
  6735. x1: {value: x1, scale: "x", type: x1 != null && x2 == null ? "band" : undefined, optional: true},
  6736. y1: {value: y1, scale: "y", type: y1 != null && y2 == null ? "band" : undefined, optional: true},
  6737. x2: {value: x2, scale: "x", optional: true},
  6738. y2: {value: y2, scale: "y", optional: true}
  6739. },
  6740. options,
  6741. defaults$h
  6742. );
  6743. rectInsets(this, options);
  6744. rectRadii(this, options);
  6745. }
  6746. render(index, scales, channels, dimensions, context) {
  6747. const {x, y} = scales;
  6748. let {x1: X1, y1: Y1, x2: X2, y2: Y2} = channels;
  6749. const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
  6750. const {projection} = context;
  6751. const {insetTop, insetRight, insetBottom, insetLeft} = this;
  6752. const {rx, ry, rx1y1, rx1y2, rx2y1, rx2y2} = this;
  6753. if ((X1 || X2) && !projection && isCollapsed(x)) X1 = X2 = null; // ignore if collapsed
  6754. if ((Y1 || Y2) && !projection && isCollapsed(y)) Y1 = Y2 = null; // ignore if collapsed
  6755. const bx = x?.bandwidth ? x.bandwidth() : 0;
  6756. const by = y?.bandwidth ? y.bandwidth() : 0;
  6757. return create("svg:g", context)
  6758. .call(applyIndirectStyles, this, dimensions, context)
  6759. .call(applyTransform, this, {}, 0, 0)
  6760. .call((g) =>
  6761. g
  6762. .selectAll()
  6763. .data(index)
  6764. .enter()
  6765. .call(
  6766. rx1y1 || rx1y2 || rx2y1 || rx2y2
  6767. ? (g) =>
  6768. g
  6769. .append("path")
  6770. .call(applyDirectStyles, this)
  6771. .call(
  6772. applyRoundedRect,
  6773. X1 && X2
  6774. ? (i) => X1[i] + (X2[i] < X1[i] ? -insetRight : insetLeft)
  6775. : X1
  6776. ? (i) => X1[i] + insetLeft
  6777. : marginLeft + insetLeft,
  6778. Y1 && Y2
  6779. ? (i) => Y1[i] + (Y2[i] < Y1[i] ? -insetBottom : insetTop)
  6780. : Y1
  6781. ? (i) => Y1[i] + insetTop
  6782. : marginTop + insetTop,
  6783. X1 && X2
  6784. ? (i) => X2[i] - (X2[i] < X1[i] ? -insetLeft : insetRight)
  6785. : X1
  6786. ? (i) => X1[i] + bx - insetRight
  6787. : width - marginRight - insetRight,
  6788. Y1 && Y2
  6789. ? (i) => Y2[i] - (Y2[i] < Y1[i] ? -insetTop : insetBottom)
  6790. : Y1
  6791. ? (i) => Y1[i] + by - insetBottom
  6792. : height - marginBottom - insetBottom,
  6793. this
  6794. )
  6795. .call(applyChannelStyles, this, channels)
  6796. : (g) =>
  6797. g
  6798. .append("rect")
  6799. .call(applyDirectStyles, this)
  6800. .attr(
  6801. "x",
  6802. X1
  6803. ? X2
  6804. ? (i) => Math.min(X1[i], X2[i]) + insetLeft
  6805. : (i) => X1[i] + insetLeft
  6806. : marginLeft + insetLeft
  6807. )
  6808. .attr(
  6809. "y",
  6810. Y1
  6811. ? Y2
  6812. ? (i) => Math.min(Y1[i], Y2[i]) + insetTop
  6813. : (i) => Y1[i] + insetTop
  6814. : marginTop + insetTop
  6815. )
  6816. .attr(
  6817. "width",
  6818. X1
  6819. ? X2
  6820. ? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) + bx - insetLeft - insetRight)
  6821. : bx - insetLeft - insetRight
  6822. : width - marginRight - marginLeft - insetRight - insetLeft
  6823. )
  6824. .attr(
  6825. "height",
  6826. Y1
  6827. ? Y2
  6828. ? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) + by - insetTop - insetBottom)
  6829. : by - insetTop - insetBottom
  6830. : height - marginTop - marginBottom - insetTop - insetBottom
  6831. )
  6832. .call(applyAttr, "rx", rx)
  6833. .call(applyAttr, "ry", ry)
  6834. .call(applyChannelStyles, this, channels)
  6835. )
  6836. )
  6837. .node();
  6838. }
  6839. }
  6840. function rectInsets(
  6841. mark,
  6842. {inset = 0, insetTop = inset, insetRight = inset, insetBottom = inset, insetLeft = inset} = {}
  6843. ) {
  6844. mark.insetTop = number$1(insetTop);
  6845. mark.insetRight = number$1(insetRight);
  6846. mark.insetBottom = number$1(insetBottom);
  6847. mark.insetLeft = number$1(insetLeft);
  6848. }
  6849. function rectRadii(
  6850. mark,
  6851. {
  6852. r,
  6853. rx, // for elliptic corners
  6854. ry, // for elliptic corners
  6855. rx1 = r,
  6856. ry1 = r,
  6857. rx2 = r,
  6858. ry2 = r,
  6859. rx1y1 = rx1 !== undefined ? +rx1 : ry1 !== undefined ? +ry1 : 0,
  6860. rx1y2 = rx1 !== undefined ? +rx1 : ry2 !== undefined ? +ry2 : 0,
  6861. rx2y1 = rx2 !== undefined ? +rx2 : ry1 !== undefined ? +ry1 : 0,
  6862. rx2y2 = rx2 !== undefined ? +rx2 : ry2 !== undefined ? +ry2 : 0
  6863. } = {}
  6864. ) {
  6865. if (rx1y1 || rx1y2 || rx2y1 || rx2y2) {
  6866. mark.rx1y1 = rx1y1;
  6867. mark.rx1y2 = rx1y2;
  6868. mark.rx2y1 = rx2y1;
  6869. mark.rx2y2 = rx2y2;
  6870. } else {
  6871. mark.rx = impliedString(rx, "auto"); // number or percentage
  6872. mark.ry = impliedString(ry, "auto");
  6873. }
  6874. }
  6875. function applyRoundedRect(selection, X1, Y1, X2, Y2, mark) {
  6876. const {rx1y1: r11, rx1y2: r12, rx2y1: r21, rx2y2: r22} = mark;
  6877. if (typeof X1 !== "function") X1 = constant(X1);
  6878. if (typeof Y1 !== "function") Y1 = constant(Y1);
  6879. if (typeof X2 !== "function") X2 = constant(X2);
  6880. if (typeof Y2 !== "function") Y2 = constant(Y2);
  6881. const rx = Math.max(Math.abs(r11 + r21), Math.abs(r12 + r22));
  6882. const ry = Math.max(Math.abs(r11 + r12), Math.abs(r21 + r22));
  6883. selection.attr("d", (i) => {
  6884. const x1 = X1(i);
  6885. const y1 = Y1(i);
  6886. const x2 = X2(i);
  6887. const y2 = Y2(i);
  6888. const ix = x1 > x2;
  6889. const iy = y1 > y2;
  6890. const l = ix ? x2 : x1;
  6891. const r = ix ? x1 : x2;
  6892. const t = iy ? y2 : y1;
  6893. const b = iy ? y1 : y2;
  6894. const k = Math.min(1, (r - l) / rx, (b - t) / ry);
  6895. const tl = k * (ix ? (iy ? r22 : r21) : iy ? r12 : r11);
  6896. const tr = k * (ix ? (iy ? r12 : r11) : iy ? r22 : r21);
  6897. const br = k * (ix ? (iy ? r11 : r12) : iy ? r21 : r22);
  6898. const bl = k * (ix ? (iy ? r21 : r22) : iy ? r11 : r12);
  6899. return (
  6900. `M${l},${t + biasY(tl, bl)}A${tl},${tl} 0 0 ${tl < 0 ? 0 : 1} ${l + biasX(tl, bl)},${t}` +
  6901. `H${r - biasX(tr, br)}A${tr},${tr} 0 0 ${tr < 0 ? 0 : 1} ${r},${t + biasY(tr, br)}` +
  6902. `V${b - biasY(br, tr)}A${br},${br} 0 0 ${br < 0 ? 0 : 1} ${r - biasX(br, tr)},${b}` +
  6903. `H${l + biasX(bl, tl)}A${bl},${bl} 0 0 ${bl < 0 ? 0 : 1} ${l},${b - biasY(bl, tl)}` +
  6904. `Z`
  6905. );
  6906. });
  6907. }
  6908. /**
  6909. * If the opposing corner has a negative radius r2, if this corner has a
  6910. * negative radius r1, this corner’s “wing” will extend horizontally rather than
  6911. * vertically.
  6912. */
  6913. function biasX(r1, r2) {
  6914. return r2 < 0 ? r1 : Math.abs(r1);
  6915. }
  6916. /**
  6917. * If the opposing corner has a negative radius r2, if this corner has a
  6918. * negative radius r1, this corner’s “wing” will extend horizontally rather than
  6919. * vertically.
  6920. */
  6921. function biasY(r1, r2) {
  6922. return r2 < 0 ? Math.abs(r1) : r1;
  6923. }
  6924. function rect(data, options) {
  6925. return new Rect(data, maybeTrivialIntervalX(maybeTrivialIntervalY(options)));
  6926. }
  6927. function rectX(data, options = {}) {
  6928. if (!hasXY(options)) options = {...options, y: indexOf, x2: identity$1, interval: 1};
  6929. return new Rect(data, maybeStackX(maybeTrivialIntervalY(maybeIdentityX(options))));
  6930. }
  6931. function rectY(data, options = {}) {
  6932. if (!hasXY(options)) options = {...options, x: indexOf, y2: identity$1, interval: 1};
  6933. return new Rect(data, maybeStackY(maybeTrivialIntervalX(maybeIdentityY(options))));
  6934. }
  6935. const defaults$g = {
  6936. ariaLabel: "frame",
  6937. fill: "none",
  6938. stroke: "currentColor",
  6939. clip: false
  6940. };
  6941. const lineDefaults = {
  6942. ariaLabel: "frame",
  6943. fill: null,
  6944. stroke: "currentColor",
  6945. strokeLinecap: "square",
  6946. clip: false
  6947. };
  6948. class Frame extends Mark {
  6949. constructor(options = {}) {
  6950. const {anchor = null} = options;
  6951. super(singleton, undefined, options, anchor == null ? defaults$g : lineDefaults);
  6952. this.anchor = maybeKeyword(anchor, "anchor", ["top", "right", "bottom", "left"]);
  6953. rectInsets(this, options);
  6954. if (!anchor) rectRadii(this, options);
  6955. }
  6956. render(index, scales, channels, dimensions, context) {
  6957. const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
  6958. const {anchor, insetTop, insetRight, insetBottom, insetLeft} = this;
  6959. const {rx, ry, rx1y1, rx1y2, rx2y1, rx2y2} = this;
  6960. const x1 = marginLeft + insetLeft;
  6961. const x2 = width - marginRight - insetRight;
  6962. const y1 = marginTop + insetTop;
  6963. const y2 = height - marginBottom - insetBottom;
  6964. return create(anchor ? "svg:line" : rx1y1 || rx1y2 || rx2y1 || rx2y2 ? "svg:path" : "svg:rect", context)
  6965. .datum(0)
  6966. .call(applyIndirectStyles, this, dimensions, context)
  6967. .call(applyDirectStyles, this)
  6968. .call(applyChannelStyles, this, channels)
  6969. .call(applyTransform, this, {})
  6970. .call(
  6971. anchor === "left"
  6972. ? (line) => line.attr("x1", x1).attr("x2", x1).attr("y1", y1).attr("y2", y2)
  6973. : anchor === "right"
  6974. ? (line) => line.attr("x1", x2).attr("x2", x2).attr("y1", y1).attr("y2", y2)
  6975. : anchor === "top"
  6976. ? (line) => line.attr("x1", x1).attr("x2", x2).attr("y1", y1).attr("y2", y1)
  6977. : anchor === "bottom"
  6978. ? (line) => line.attr("x1", x1).attr("x2", x2).attr("y1", y2).attr("y2", y2)
  6979. : rx1y1 || rx1y2 || rx2y1 || rx2y2
  6980. ? (path) => path.call(applyRoundedRect, x1, y1, x2, y2, this)
  6981. : (rect) =>
  6982. rect
  6983. .attr("x", x1)
  6984. .attr("y", y1)
  6985. .attr("width", x2 - x1)
  6986. .attr("height", y2 - y1)
  6987. .attr("rx", rx)
  6988. .attr("ry", ry)
  6989. )
  6990. .node();
  6991. }
  6992. }
  6993. function frame(options) {
  6994. return new Frame(options);
  6995. }
  6996. const defaults$f = {
  6997. ariaLabel: "tip",
  6998. fill: "var(--plot-background)",
  6999. stroke: "currentColor"
  7000. };
  7001. // These channels are not displayed in the default tip; see formatChannels.
  7002. const ignoreChannels = new Set(["geometry", "href", "src", "ariaLabel", "scales"]);
  7003. class Tip extends Mark {
  7004. constructor(data, options = {}) {
  7005. if (options.tip) options = {...options, tip: false};
  7006. if (options.title === undefined && isIterable(data) && isTextual(data)) options = {...options, title: identity$1};
  7007. const {
  7008. x,
  7009. y,
  7010. x1,
  7011. x2,
  7012. y1,
  7013. y2,
  7014. anchor,
  7015. preferredAnchor = "bottom",
  7016. monospace,
  7017. fontFamily = monospace ? "ui-monospace, monospace" : undefined,
  7018. fontSize,
  7019. fontStyle,
  7020. fontVariant,
  7021. fontWeight,
  7022. lineHeight = 1,
  7023. lineWidth = 20,
  7024. frameAnchor,
  7025. format,
  7026. textAnchor = "start",
  7027. textOverflow,
  7028. textPadding = 8,
  7029. title,
  7030. pointerSize = 12,
  7031. pathFilter = "drop-shadow(0 3px 4px rgba(0,0,0,0.2))"
  7032. } = options;
  7033. super(
  7034. data,
  7035. {
  7036. x: {value: x1 != null && x2 != null ? null : x, scale: "x", optional: true}, // ignore midpoint
  7037. y: {value: y1 != null && y2 != null ? null : y, scale: "y", optional: true}, // ignore midpoint
  7038. x1: {value: x1, scale: "x", optional: x2 == null},
  7039. y1: {value: y1, scale: "y", optional: y2 == null},
  7040. x2: {value: x2, scale: "x", optional: x1 == null},
  7041. y2: {value: y2, scale: "y", optional: y1 == null},
  7042. title: {value: title, optional: true} // filter: defined
  7043. },
  7044. options,
  7045. defaults$f
  7046. );
  7047. this.anchor = maybeAnchor$3(anchor, "anchor");
  7048. this.preferredAnchor = maybeAnchor$3(preferredAnchor, "preferredAnchor");
  7049. this.frameAnchor = maybeFrameAnchor(frameAnchor);
  7050. this.textAnchor = impliedString(textAnchor, "middle");
  7051. this.textPadding = +textPadding;
  7052. this.pointerSize = +pointerSize;
  7053. this.pathFilter = string(pathFilter);
  7054. this.lineHeight = +lineHeight;
  7055. this.lineWidth = +lineWidth;
  7056. this.textOverflow = maybeTextOverflow(textOverflow);
  7057. this.monospace = !!monospace;
  7058. this.fontFamily = string(fontFamily);
  7059. this.fontSize = number$1(fontSize);
  7060. this.fontStyle = string(fontStyle);
  7061. this.fontVariant = string(fontVariant);
  7062. this.fontWeight = string(fontWeight);
  7063. for (const key in defaults$f) if (key in this.channels) this[key] = defaults$f[key]; // apply default even if channel
  7064. this.splitLines = splitter(this);
  7065. this.clipLine = clipper(this);
  7066. this.format = typeof format === "string" || typeof format === "function" ? {title: format} : {...format}; // defensive copy before mutate; also promote nullish to empty
  7067. }
  7068. render(index, scales, values, dimensions, context) {
  7069. const mark = this;
  7070. const {x, y, fx, fy} = scales;
  7071. const {ownerSVGElement: svg, document} = context;
  7072. const {anchor, monospace, lineHeight, lineWidth} = this;
  7073. const {textPadding: r, pointerSize: m, pathFilter} = this;
  7074. const {marginTop, marginLeft} = dimensions;
  7075. // The anchor position is the middle of x1 & y1 and x2 & y2, if available,
  7076. // or x & y; the former is considered more specific because it’s how we
  7077. // disable the implicit stack and interval transforms. If any dimension is
  7078. // unspecified, we fallback to the frame anchor. We also need to know the
  7079. // facet offsets to detect when the tip would draw outside the plot, and
  7080. // thus we need to change the orientation.
  7081. const {x1: X1, y1: Y1, x2: X2, y2: Y2, x: X = X1 ?? X2, y: Y = Y1 ?? Y2} = values;
  7082. const ox = fx ? fx(index.fx) - marginLeft : 0;
  7083. const oy = fy ? fy(index.fy) - marginTop : 0;
  7084. // The order of precedence for the anchor position is: the middle of x1 & y1
  7085. // and x2 & y2; or x1 & y1 (e.g., area); or lastly x & y. If a dimension is
  7086. // unspecified, the frame anchor is used.
  7087. const [cx, cy] = applyFrameAnchor(this, dimensions);
  7088. const px = anchorX$1(values, cx);
  7089. const py = anchorY$1(values, cy);
  7090. // Resolve the text metric implementation. We may need an ellipsis for text
  7091. // truncation, so we optimistically compute the ellipsis width.
  7092. const widthof = monospace ? monospaceWidth : defaultWidth;
  7093. const ee = widthof(ellipsis);
  7094. // If there’s a title channel, display that as-is; otherwise, show multiple
  7095. // channels as name-value pairs.
  7096. let sources, format;
  7097. if ("title" in values) {
  7098. sources = getSourceChannels.call(this, {title: values.channels.title}, scales);
  7099. format = formatTitle;
  7100. } else {
  7101. sources = getSourceChannels.call(this, values.channels, scales);
  7102. format = formatChannels;
  7103. }
  7104. // We don’t call applyChannelStyles because we only use the channels to
  7105. // derive the content of the tip, not its aesthetics.
  7106. const g = create("svg:g", context)
  7107. .call(applyIndirectStyles, this, dimensions, context)
  7108. .call(applyIndirectTextStyles, this)
  7109. .call(applyTransform, this, {x: X && x, y: Y && y})
  7110. .call((g) =>
  7111. g
  7112. .selectAll()
  7113. .data(index)
  7114. .enter()
  7115. .append("g")
  7116. .attr("transform", (i) => `translate(${Math.round(px(i))},${Math.round(py(i))})`) // crisp edges
  7117. .call(applyDirectStyles, this)
  7118. .call((g) => g.append("path").attr("filter", pathFilter))
  7119. .call((g) =>
  7120. g.append("text").each(function (i) {
  7121. const that = d3.select(this);
  7122. // prevent style inheritance (from path)
  7123. this.setAttribute("fill", "currentColor");
  7124. this.setAttribute("fill-opacity", 1);
  7125. this.setAttribute("stroke", "none");
  7126. // iteratively render each channel value
  7127. const lines = format.call(mark, i, index, sources, scales, values);
  7128. if (typeof lines === "string") {
  7129. for (const line of mark.splitLines(lines)) {
  7130. renderLine(that, {value: mark.clipLine(line)});
  7131. }
  7132. } else {
  7133. const labels = new Set();
  7134. for (const line of lines) {
  7135. const {label = ""} = line;
  7136. if (label && labels.has(label)) continue;
  7137. else labels.add(label);
  7138. renderLine(that, line);
  7139. }
  7140. }
  7141. })
  7142. )
  7143. );
  7144. // Renders a single line (a name-value pair) to the tip, truncating the text
  7145. // as needed, and adding a title if the text is truncated. Note that this is
  7146. // just the initial layout of the text; in postrender we will compute the
  7147. // exact text metrics and translate the text as needed once we know the
  7148. // tip’s orientation (anchor).
  7149. function renderLine(selection, {label, value, color, opacity}) {
  7150. (label ??= ""), (value ??= "");
  7151. const swatch = color != null || opacity != null;
  7152. let title;
  7153. let w = lineWidth * 100;
  7154. const [j] = cut(label, w, widthof, ee);
  7155. if (j >= 0) {
  7156. // label is truncated
  7157. label = label.slice(0, j).trimEnd() + ellipsis;
  7158. title = value.trim();
  7159. value = "";
  7160. } else {
  7161. if (label || (!value && !swatch)) value = " " + value;
  7162. const [k] = cut(value, w - widthof(label), widthof, ee);
  7163. if (k >= 0) {
  7164. // value is truncated
  7165. title = value.trim();
  7166. value = value.slice(0, k).trimEnd() + ellipsis;
  7167. }
  7168. }
  7169. const line = selection.append("tspan").attr("x", 0).attr("dy", `${lineHeight}em`).text("\u200b"); // zwsp for double-click
  7170. if (label) line.append("tspan").attr("font-weight", "bold").text(label);
  7171. if (value) line.append(() => document.createTextNode(value));
  7172. if (swatch) line.append("tspan").text(" ■").attr("fill", color).attr("fill-opacity", opacity).style("user-select", "none"); // prettier-ignore
  7173. if (title) line.append("title").text(title);
  7174. }
  7175. // Only after the plot is attached to the page can we compute the exact text
  7176. // metrics needed to determine the tip size and orientation (anchor).
  7177. function postrender() {
  7178. const {width, height} = dimensions.facet ?? dimensions;
  7179. g.selectChildren().each(function (i) {
  7180. let {x: tx, width: w, height: h} = this.getBBox();
  7181. (w = Math.round(w)), (h = Math.round(h)); // crisp edges
  7182. let a = anchor; // use the specified anchor, if any
  7183. if (a === undefined) {
  7184. const x = px(i) + ox;
  7185. const y = py(i) + oy;
  7186. const fitLeft = x + w + m + r * 2 < width;
  7187. const fitRight = x - w - m - r * 2 > 0;
  7188. const fitTop = y + h + m + r * 2 < height;
  7189. const fitBottom = y - h - m - r * 2 > 0;
  7190. a =
  7191. fitLeft && fitRight
  7192. ? fitTop && fitBottom
  7193. ? mark.preferredAnchor
  7194. : fitBottom
  7195. ? "bottom"
  7196. : "top"
  7197. : fitTop && fitBottom
  7198. ? fitLeft
  7199. ? "left"
  7200. : "right"
  7201. : (fitLeft || fitRight) && (fitTop || fitBottom)
  7202. ? `${fitBottom ? "bottom" : "top"}-${fitLeft ? "left" : "right"}`
  7203. : mark.preferredAnchor;
  7204. }
  7205. const path = this.firstChild; // note: assumes exactly two children!
  7206. const text = this.lastChild; // note: assumes exactly two children!
  7207. path.setAttribute("d", getPath(a, m, r, w, h));
  7208. if (tx) for (const t of text.childNodes) t.setAttribute("x", -tx);
  7209. text.setAttribute("y", `${+getLineOffset(a, text.childNodes.length, lineHeight).toFixed(6)}em`);
  7210. text.setAttribute("transform", `translate(${getTextTranslate(a, m, r, w, h)})`);
  7211. });
  7212. g.attr("visibility", null);
  7213. }
  7214. // Wait until the plot is inserted into the page so that we can use getBBox
  7215. // to compute the exact text dimensions. If the SVG is already connected, as
  7216. // when the pointer interaction triggers the re-render, use a faster
  7217. // microtask instead of an animation frame; if this SSR (e.g., JSDOM), skip
  7218. // this step. Perhaps this could be done synchronously; getting the
  7219. // dimensions of the SVG is easy, and although accurate text metrics are
  7220. // hard, we could use approximate heuristics.
  7221. if (index.length) {
  7222. g.attr("visibility", "hidden"); // hide until postrender
  7223. if (svg.isConnected) Promise.resolve().then(postrender);
  7224. else if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(postrender);
  7225. }
  7226. return g.node();
  7227. }
  7228. }
  7229. function tip(data, {x, y, ...options} = {}) {
  7230. if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y);
  7231. return new Tip(data, {...options, x, y});
  7232. }
  7233. function getLineOffset(anchor, length, lineHeight) {
  7234. return /^top(?:-|$)/.test(anchor)
  7235. ? 0.94 - lineHeight
  7236. : /^bottom(?:-|$)/
  7237. ? -0.29 - length * lineHeight
  7238. : (length / 2) * lineHeight;
  7239. }
  7240. function getTextTranslate(anchor, m, r, width, height) {
  7241. switch (anchor) {
  7242. case "middle":
  7243. return [-width / 2, height / 2];
  7244. case "top-left":
  7245. return [r, m + r];
  7246. case "top":
  7247. return [-width / 2, m / 2 + r];
  7248. case "top-right":
  7249. return [-width - r, m + r];
  7250. case "right":
  7251. return [-m / 2 - width - r, height / 2];
  7252. case "bottom-left":
  7253. return [r, -m - r];
  7254. case "bottom":
  7255. return [-width / 2, -m / 2 - r];
  7256. case "bottom-right":
  7257. return [-width - r, -m - r];
  7258. case "left":
  7259. return [r + m / 2, height / 2];
  7260. }
  7261. }
  7262. function getPath(anchor, m, r, width, height) {
  7263. const w = width + r * 2;
  7264. const h = height + r * 2;
  7265. switch (anchor) {
  7266. case "middle":
  7267. return `M${-w / 2},${-h / 2}h${w}v${h}h${-w}z`;
  7268. case "top-left":
  7269. return `M0,0l${m},${m}h${w - m}v${h}h${-w}z`;
  7270. case "top":
  7271. return `M0,0l${m / 2},${m / 2}h${(w - m) / 2}v${h}h${-w}v${-h}h${(w - m) / 2}z`;
  7272. case "top-right":
  7273. return `M0,0l${-m},${m}h${m - w}v${h}h${w}z`;
  7274. case "right":
  7275. return `M0,0l${-m / 2},${-m / 2}v${m / 2 - h / 2}h${-w}v${h}h${w}v${m / 2 - h / 2}z`;
  7276. case "bottom-left":
  7277. return `M0,0l${m},${-m}h${w - m}v${-h}h${-w}z`;
  7278. case "bottom":
  7279. return `M0,0l${m / 2},${-m / 2}h${(w - m) / 2}v${-h}h${-w}v${h}h${(w - m) / 2}z`;
  7280. case "bottom-right":
  7281. return `M0,0l${-m},${-m}h${m - w}v${-h}h${w}z`;
  7282. case "left":
  7283. return `M0,0l${m / 2},${-m / 2}v${m / 2 - h / 2}h${w}v${h}h${-w}v${m / 2 - h / 2}z`;
  7284. }
  7285. }
  7286. // Note: mutates this.format!
  7287. function getSourceChannels(channels, scales) {
  7288. const sources = {};
  7289. // Promote x and y shorthand for paired channels (in order).
  7290. let format = this.format;
  7291. format = maybeExpandPairedFormat(format, channels, "x");
  7292. format = maybeExpandPairedFormat(format, channels, "y");
  7293. this.format = format;
  7294. // Prioritize channels with explicit formats, in the given order.
  7295. for (const key in format) {
  7296. const value = format[key];
  7297. if (value === null || value === false) {
  7298. continue;
  7299. } else if (key === "fx" || key === "fy") {
  7300. sources[key] = true;
  7301. } else {
  7302. const source = getSource(channels, key);
  7303. if (source) sources[key] = source;
  7304. }
  7305. }
  7306. // Then fallback to all other (non-ignored) channels.
  7307. for (const key in channels) {
  7308. if (key in sources || key in format || ignoreChannels.has(key)) continue;
  7309. if ((key === "x" || key === "y") && channels.geometry) continue; // ignore x & y on geo
  7310. const source = getSource(channels, key);
  7311. if (source) {
  7312. // Ignore color channels if the values are all literal colors.
  7313. if (source.scale == null && source.defaultScale === "color") continue;
  7314. sources[key] = source;
  7315. }
  7316. }
  7317. // And lastly facet channels, but only if this mark is faceted.
  7318. if (this.facet) {
  7319. if (scales.fx && !("fx" in format)) sources.fx = true;
  7320. if (scales.fy && !("fy" in format)) sources.fy = true;
  7321. }
  7322. // Promote shorthand string formats, and materialize default formats.
  7323. for (const key in sources) {
  7324. const format = this.format[key];
  7325. if (typeof format === "string") {
  7326. const value = sources[key]?.value ?? scales[key]?.domain() ?? [];
  7327. this.format[key] = (isTemporal(value) ? d3.utcFormat : d3.format)(format);
  7328. } else if (format === undefined || format === true) {
  7329. // For ordinal scales, the inferred tick format can be more concise, such
  7330. // as only showing the year for yearly data.
  7331. const scale = scales[key];
  7332. this.format[key] = scale?.bandwidth ? inferTickFormat(scale, scale.domain()) : formatDefault;
  7333. }
  7334. }
  7335. return sources;
  7336. }
  7337. // Promote x and y shorthand for paired channels, while preserving order.
  7338. function maybeExpandPairedFormat(format, channels, key) {
  7339. if (!(key in format)) return format;
  7340. const key1 = `${key}1`;
  7341. const key2 = `${key}2`;
  7342. if ((key1 in format || !(key1 in channels)) && (key2 in format || !(key2 in channels))) return format;
  7343. const entries = Object.entries(format);
  7344. const value = format[key];
  7345. entries.splice(entries.findIndex(([name]) => name === key) + 1, 0, [key1, value], [key2, value]);
  7346. return Object.fromEntries(entries);
  7347. }
  7348. function formatTitle(i, index, {title}) {
  7349. return this.format.title(title.value[i], i);
  7350. }
  7351. function* formatChannels(i, index, channels, scales, values) {
  7352. for (const key in channels) {
  7353. if (key === "fx" || key === "fy") {
  7354. yield {
  7355. label: formatLabel(scales, channels, key),
  7356. value: this.format[key](index[key], i)
  7357. };
  7358. continue;
  7359. }
  7360. if (key === "x1" && "x2" in channels) continue;
  7361. if (key === "y1" && "y2" in channels) continue;
  7362. const channel = channels[key];
  7363. if (key === "x2" && "x1" in channels) {
  7364. yield {
  7365. label: formatPairLabel(scales, channels, "x"),
  7366. value: formatPair(this.format.x2, channels.x1, channel, i)
  7367. };
  7368. } else if (key === "y2" && "y1" in channels) {
  7369. yield {
  7370. label: formatPairLabel(scales, channels, "y"),
  7371. value: formatPair(this.format.y2, channels.y1, channel, i)
  7372. };
  7373. } else {
  7374. const value = channel.value[i];
  7375. const scale = channel.scale;
  7376. if (!defined(value) && scale == null) continue;
  7377. yield {
  7378. label: formatLabel(scales, channels, key),
  7379. value: this.format[key](value, i),
  7380. color: scale === "color" ? values[key][i] : null,
  7381. opacity: scale === "opacity" ? values[key][i] : null
  7382. };
  7383. }
  7384. }
  7385. }
  7386. function formatPair(formatValue, c1, c2, i) {
  7387. return c2.hint?.length // e.g., stackY’s y1 and y2
  7388. ? `${formatValue(c2.value[i] - c1.value[i], i)}`
  7389. : `${formatValue(c1.value[i], i)}–${formatValue(c2.value[i], i)}`;
  7390. }
  7391. function formatPairLabel(scales, channels, key) {
  7392. const l1 = formatLabel(scales, channels, `${key}1`, key);
  7393. const l2 = formatLabel(scales, channels, `${key}2`, key);
  7394. return l1 === l2 ? l1 : `${l1}–${l2}`;
  7395. }
  7396. function formatLabel(scales, channels, key, defaultLabel = key) {
  7397. const channel = channels[key];
  7398. const scale = scales[channel?.scale ?? key];
  7399. return String(scale?.label ?? channel?.label ?? defaultLabel);
  7400. }
  7401. function plot(options = {}) {
  7402. const {facet, style, title, subtitle, caption, ariaLabel, ariaDescription} = options;
  7403. // className for inline styles
  7404. const className = maybeClassName(options.className);
  7405. // Flatten any nested marks.
  7406. const marks = options.marks === undefined ? [] : flatMarks(options.marks);
  7407. // Add implicit tips.
  7408. marks.push(...inferTips(marks));
  7409. // Compute the top-level facet state. This has roughly the same structure as
  7410. // mark-specific facet state, except there isn’t a facetsIndex, and there’s a
  7411. // data and dataLength so we can warn the user if a different data of the same
  7412. // length is used in a mark.
  7413. const topFacetState = maybeTopFacet(facet, options);
  7414. // Construct a map from (faceted) Mark instance to facet state, including:
  7415. // channels - an {fx?, fy?} object to add to the fx and fy scale
  7416. // groups - a possibly-nested map from facet values to indexes in the data array
  7417. // facetsIndex - a sparse nested array of indices corresponding to the valid facets
  7418. const facetStateByMark = new Map();
  7419. for (const mark of marks) {
  7420. const facetState = maybeMarkFacet(mark, topFacetState, options);
  7421. if (facetState) facetStateByMark.set(mark, facetState);
  7422. }
  7423. // Compute a Map from scale name to an array of associated channels.
  7424. const channelsByScale = new Map();
  7425. if (topFacetState) addScaleChannels(channelsByScale, [topFacetState], options);
  7426. addScaleChannels(channelsByScale, facetStateByMark, options);
  7427. // Add implicit axis marks. Because this happens after faceting (because it
  7428. // depends on whether faceting is present), we must initialize the facet state
  7429. // of any implicit axes, too.
  7430. const axes = flatMarks(inferAxes(marks, channelsByScale, options));
  7431. for (const mark of axes) {
  7432. const facetState = maybeMarkFacet(mark, topFacetState, options);
  7433. if (facetState) facetStateByMark.set(mark, facetState);
  7434. }
  7435. marks.unshift(...axes);
  7436. // All the possible facets are given by the domains of the fx or fy scales, or
  7437. // the cross-product of these domains if we facet by both x and y. We sort
  7438. // them in order to apply the facet filters afterwards.
  7439. let facets = createFacets(channelsByScale, options);
  7440. if (facets !== undefined) {
  7441. const topFacetsIndex = topFacetState ? facetFilter(facets, topFacetState) : undefined;
  7442. // Compute a facet index for each mark, parallel to the facets array. For
  7443. // mark-level facets, compute an index for that mark’s data and options.
  7444. // Otherwise, use the top-level facet index.
  7445. for (const mark of marks) {
  7446. if (mark.facet === null || mark.facet === "super") continue;
  7447. const facetState = facetStateByMark.get(mark);
  7448. if (facetState === undefined) continue;
  7449. facetState.facetsIndex = mark.fx != null || mark.fy != null ? facetFilter(facets, facetState) : topFacetsIndex;
  7450. }
  7451. // The cross product of the domains of fx and fy can include fx-fy
  7452. // combinations for which no mark has an instance associated with that
  7453. // combination, and therefore we don’t want to render this facet (not even
  7454. // the frame). The same can occur if you specify the domain of fx and fy
  7455. // explicitly, but there is no mark instance associated with some values in
  7456. // the domain. Expunge empty facets, and clear the corresponding elements
  7457. // from the nested index in each mark.
  7458. const nonEmpty = new Set();
  7459. for (const {facetsIndex} of facetStateByMark.values()) {
  7460. facetsIndex?.forEach((index, i) => {
  7461. if (index?.length > 0) {
  7462. nonEmpty.add(i);
  7463. }
  7464. });
  7465. }
  7466. // If all the facets are empty (as when none of the marks are actually
  7467. // faceted), none of them are empty.
  7468. facets.forEach(
  7469. 0 < nonEmpty.size && nonEmpty.size < facets.length
  7470. ? (f, i) => (f.empty = !nonEmpty.has(i))
  7471. : (f) => (f.empty = false)
  7472. );
  7473. // For any mark using the “exclude” facet mode, invert the index.
  7474. for (const mark of marks) {
  7475. if (mark.facet === "exclude") {
  7476. const facetState = facetStateByMark.get(mark);
  7477. if (facetState !== undefined) facetState.facetsIndex = facetExclude(facetState.facetsIndex);
  7478. }
  7479. }
  7480. }
  7481. // If a scale is explicitly declared in options, initialize its associated
  7482. // channels to the empty array; this will guarantee that a corresponding scale
  7483. // will be created later (even if there are no other channels). Ignore facet
  7484. // scale declarations, which are handled above.
  7485. for (const key of registry.keys()) {
  7486. if (isScaleOptions(options[key]) && key !== "fx" && key !== "fy") {
  7487. channelsByScale.set(key, []);
  7488. }
  7489. }
  7490. // A Map from Mark instance to its render state, including:
  7491. // index - the data index e.g. [0, 1, 2, 3, …]
  7492. // channels - an array of materialized channels e.g. [["x", {value}], …]
  7493. // faceted - a boolean indicating whether this mark is faceted
  7494. // values - an object of scaled values e.g. {x: [40, 32, …], …}
  7495. const stateByMark = new Map();
  7496. // Initialize the marks’ state.
  7497. for (const mark of marks) {
  7498. if (stateByMark.has(mark)) throw new Error("duplicate mark; each mark must be unique");
  7499. const {facetsIndex, channels: facetChannels} = facetStateByMark.get(mark) ?? {};
  7500. const {data, facets, channels} = mark.initialize(facetsIndex, facetChannels, options);
  7501. applyScaleTransforms(channels, options);
  7502. stateByMark.set(mark, {data, facets, channels});
  7503. }
  7504. // Initalize the scales and dimensions.
  7505. const scaleDescriptors = createScales(addScaleChannels(channelsByScale, stateByMark, options), options);
  7506. const dimensions = createDimensions(scaleDescriptors, marks, options);
  7507. autoScaleRange(scaleDescriptors, dimensions);
  7508. const scales = createScaleFunctions(scaleDescriptors);
  7509. const {fx, fy} = scales;
  7510. const subdimensions = fx || fy ? innerDimensions(scaleDescriptors, dimensions) : dimensions;
  7511. const superdimensions = fx || fy ? actualDimensions(scales, dimensions) : dimensions;
  7512. // Initialize the context.
  7513. const context = createContext(options);
  7514. const document = context.document;
  7515. const svg = d3.creator("svg").call(document.documentElement);
  7516. let figure = svg; // replaced with the figure element, if any
  7517. context.ownerSVGElement = svg;
  7518. context.className = className;
  7519. context.projection = createProjection(options, subdimensions);
  7520. // A path generator for marks that want to draw GeoJSON.
  7521. context.path = function () {
  7522. return d3.geoPath(this.projection ?? xyProjection(scales));
  7523. };
  7524. // Allows e.g. the axis mark to determine faceting lazily.
  7525. context.filterFacets = (data, channels) => {
  7526. return facetFilter(facets, {channels, groups: facetGroups(data, channels)});
  7527. };
  7528. // Allows e.g. the tip mark to reference channels and data on other marks.
  7529. context.getMarkState = (mark) => {
  7530. const state = stateByMark.get(mark);
  7531. const facetState = facetStateByMark.get(mark);
  7532. return {...state, channels: {...state.channels, ...facetState?.channels}};
  7533. };
  7534. // Allows e.g. the pointer transform to support viewof.
  7535. context.dispatchValue = (value) => {
  7536. if (figure.value === value) return;
  7537. figure.value = value;
  7538. figure.dispatchEvent(new context.document.defaultView.Event("input", {bubbles: true}));
  7539. };
  7540. // Reinitialize; for deriving channels dependent on other channels.
  7541. const newByScale = new Set();
  7542. for (const [mark, state] of stateByMark) {
  7543. if (mark.initializer != null) {
  7544. const dimensions = mark.facet === "super" ? superdimensions : subdimensions;
  7545. const update = mark.initializer(state.data, state.facets, state.channels, scales, dimensions, context);
  7546. if (update.data !== undefined) {
  7547. state.data = update.data;
  7548. }
  7549. if (update.facets !== undefined) {
  7550. state.facets = update.facets;
  7551. }
  7552. if (update.channels !== undefined) {
  7553. const {fx, fy, ...channels} = update.channels; // separate facet channels
  7554. inferChannelScales(channels);
  7555. Object.assign(state.channels, channels);
  7556. for (const channel of Object.values(channels)) {
  7557. const {scale} = channel;
  7558. // Initializers aren’t allowed to redefine position scales as this
  7559. // would introduce a circular dependency; so simply scale these
  7560. // channels as-is rather than creating new scales, and assume that
  7561. // they already have the scale’s transform applied, if any (e.g., when
  7562. // generating ticks for the axis mark).
  7563. if (scale != null && !isPosition(registry.get(scale))) {
  7564. applyScaleTransform(channel, options);
  7565. newByScale.add(scale);
  7566. }
  7567. }
  7568. // If the initializer returns new mark-level facet channels, we must
  7569. // record that the mark is now faceted. Note: we aren’t actually
  7570. // populating the facet state, but subsequently we won’t need it.
  7571. if (fx != null || fy != null) facetStateByMark.set(mark, true);
  7572. }
  7573. }
  7574. }
  7575. // Reconstruct scales if new scaled channels were created during
  7576. // reinitialization. Preserve existing scale labels, if any.
  7577. if (newByScale.size) {
  7578. const newChannelsByScale = new Map();
  7579. addScaleChannels(newChannelsByScale, stateByMark, options, (key) => newByScale.has(key));
  7580. addScaleChannels(channelsByScale, stateByMark, options, (key) => newByScale.has(key));
  7581. const newScaleDescriptors = inheritScaleLabels(createScales(newChannelsByScale, options), scaleDescriptors);
  7582. const {scales: newExposedScales, ...newScales} = createScaleFunctions(newScaleDescriptors);
  7583. Object.assign(scaleDescriptors, newScaleDescriptors);
  7584. Object.assign(scales, newScales);
  7585. Object.assign(scales.scales, newExposedScales);
  7586. }
  7587. // Sort and filter the facets to match the fx and fy domains; this is needed
  7588. // because the facets were constructed prior to the fx and fy scales.
  7589. let facetDomains, facetTranslate;
  7590. if (facets !== undefined) {
  7591. facetDomains = {x: fx?.domain(), y: fy?.domain()};
  7592. facets = recreateFacets(facets, facetDomains);
  7593. facetTranslate = facetTranslator(fx, fy, dimensions);
  7594. }
  7595. // Compute value objects, applying scales and projection as needed.
  7596. for (const [mark, state] of stateByMark) {
  7597. state.values = mark.scale(state.channels, scales, context);
  7598. }
  7599. const {width, height} = dimensions;
  7600. d3.select(svg)
  7601. .attr("class", className)
  7602. .attr("fill", "currentColor")
  7603. .attr("font-family", "system-ui, sans-serif")
  7604. .attr("font-size", 10)
  7605. .attr("text-anchor", "middle")
  7606. .attr("width", width)
  7607. .attr("height", height)
  7608. .attr("viewBox", `0 0 ${width} ${height}`)
  7609. .attr("aria-label", ariaLabel)
  7610. .attr("aria-description", ariaDescription)
  7611. .call((svg) =>
  7612. // Warning: if you edit this, change defaultClassName.
  7613. svg.append("style").text(
  7614. `:where(.${className}) {
  7615. --plot-background: white;
  7616. display: block;
  7617. height: auto;
  7618. height: intrinsic;
  7619. max-width: 100%;
  7620. }
  7621. :where(.${className} text),
  7622. :where(.${className} tspan) {
  7623. white-space: pre;
  7624. }`
  7625. )
  7626. )
  7627. .call(applyInlineStyles, style);
  7628. // Render marks.
  7629. for (const mark of marks) {
  7630. const {channels, values, facets: indexes} = stateByMark.get(mark);
  7631. // Render a non-faceted mark.
  7632. if (facets === undefined || mark.facet === "super") {
  7633. let index = null;
  7634. if (indexes) {
  7635. index = indexes[0];
  7636. index = mark.filter(index, channels, values);
  7637. if (index.length === 0) continue;
  7638. }
  7639. const node = mark.render(index, scales, values, superdimensions, context);
  7640. if (node == null) continue;
  7641. svg.appendChild(node);
  7642. }
  7643. // Render a faceted mark.
  7644. else {
  7645. let g;
  7646. for (const f of facets) {
  7647. if (!(mark.facetAnchor?.(facets, facetDomains, f) ?? !f.empty)) continue;
  7648. let index = null;
  7649. if (indexes) {
  7650. const faceted = facetStateByMark.has(mark);
  7651. index = indexes[faceted ? f.i : 0];
  7652. index = mark.filter(index, channels, values);
  7653. if (index.length === 0) continue;
  7654. if (!faceted && index === indexes[0]) index = subarray(index); // copy before assigning fx, fy, fi
  7655. (index.fx = f.x), (index.fy = f.y), (index.fi = f.i);
  7656. }
  7657. const node = mark.render(index, scales, values, subdimensions, context);
  7658. if (node == null) continue;
  7659. // Lazily construct the shared group (to drop empty marks).
  7660. (g ??= d3.select(svg).append("g")).append(() => node).datum(f);
  7661. // Promote ARIA attributes and mark transform to avoid repetition on
  7662. // each facet; this assumes that these attributes are consistent across
  7663. // facets, but that should be the case!
  7664. for (const name of ["aria-label", "aria-description", "aria-hidden", "transform"]) {
  7665. if (node.hasAttribute(name)) {
  7666. g.attr(name, node.getAttribute(name));
  7667. node.removeAttribute(name);
  7668. }
  7669. }
  7670. }
  7671. g?.selectChildren().each(facetTranslate);
  7672. }
  7673. }
  7674. // Wrap the plot in a figure, if needed.
  7675. const legends = createLegends(scaleDescriptors, context, options);
  7676. const {figure: figured = title != null || subtitle != null || caption != null || legends.length > 0} = options;
  7677. if (figured) {
  7678. figure = document.createElement("figure");
  7679. figure.className = `${className}-figure`;
  7680. figure.style.maxWidth = "initial"; // avoid Observable default style
  7681. if (title != null) figure.append(createTitleElement(document, title, "h2"));
  7682. if (subtitle != null) figure.append(createTitleElement(document, subtitle, "h3"));
  7683. figure.append(...legends, svg);
  7684. if (caption != null) figure.append(createFigcaption(document, caption));
  7685. if ("value" in svg) (figure.value = svg.value), delete svg.value;
  7686. }
  7687. figure.scale = exposeScales(scales.scales);
  7688. figure.legend = exposeLegends(scaleDescriptors, context, options);
  7689. const w = consumeWarnings();
  7690. if (w > 0) {
  7691. d3.select(svg)
  7692. .append("text")
  7693. .attr("x", width)
  7694. .attr("y", 20)
  7695. .attr("dy", "-1em")
  7696. .attr("text-anchor", "end")
  7697. .attr("font-family", "initial") // fix emoji rendering in Chrome
  7698. .text("\u26a0\ufe0f") // emoji variation selector
  7699. .append("title")
  7700. .text(`${w.toLocaleString("en-US")} warning${w === 1 ? "" : "s"}. Please check the console.`);
  7701. }
  7702. return figure;
  7703. }
  7704. function createTitleElement(document, contents, tag) {
  7705. if (contents.ownerDocument) return contents;
  7706. const e = document.createElement(tag);
  7707. e.append(contents);
  7708. return e;
  7709. }
  7710. function createFigcaption(document, caption) {
  7711. const e = document.createElement("figcaption");
  7712. e.append(caption);
  7713. return e;
  7714. }
  7715. function flatMarks(marks) {
  7716. return marks
  7717. .flat(Infinity)
  7718. .filter((mark) => mark != null)
  7719. .map(markify);
  7720. }
  7721. function markify(mark) {
  7722. return typeof mark.render === "function" ? mark : new Render(mark);
  7723. }
  7724. class Render extends Mark {
  7725. constructor(render) {
  7726. if (typeof render !== "function") throw new TypeError("invalid mark; missing render function");
  7727. super();
  7728. this.render = render;
  7729. }
  7730. render() {}
  7731. }
  7732. // Note: mutates channel.value to apply the scale transform, if any.
  7733. function applyScaleTransforms(channels, options) {
  7734. for (const name in channels) applyScaleTransform(channels[name], options);
  7735. return channels;
  7736. }
  7737. // Note: mutates channel.value to apply the scale transform, if any. Also sets
  7738. // channel.transform to false to prevent duplicate transform application.
  7739. function applyScaleTransform(channel, options) {
  7740. const {scale, transform: t = true} = channel;
  7741. if (scale == null || !t) return;
  7742. const {
  7743. type,
  7744. percent,
  7745. interval,
  7746. transform = percent ? (x) => (x == null ? NaN : x * 100) : maybeIntervalTransform(interval, type)
  7747. } = options[scale] ?? {};
  7748. if (transform == null) return;
  7749. channel.value = map$1(channel.value, transform);
  7750. channel.transform = false;
  7751. }
  7752. // An initializer may generate channels without knowing how the downstream mark
  7753. // will use them. Marks are typically responsible associated scales with
  7754. // channels, but here we assume common behavior across marks.
  7755. function inferChannelScales(channels) {
  7756. for (const name in channels) {
  7757. inferChannelScale(name, channels[name]);
  7758. }
  7759. }
  7760. function addScaleChannels(channelsByScale, stateByMark, options, filter = yes) {
  7761. for (const {channels} of stateByMark.values()) {
  7762. for (const name in channels) {
  7763. const channel = channels[name];
  7764. const {scale} = channel;
  7765. if (scale != null && filter(scale)) {
  7766. // Geo marks affect the default x and y domains if there is no
  7767. // projection. Skip this (as an optimization) when a projection is
  7768. // specified, or when the domains for x and y are specified.
  7769. if (scale === "projection") {
  7770. if (!hasProjection(options)) {
  7771. const gx = options.x?.domain === undefined;
  7772. const gy = options.y?.domain === undefined;
  7773. if (gx || gy) {
  7774. const [x, y] = getGeometryChannels(channel);
  7775. if (gx) addScaleChannel(channelsByScale, "x", x);
  7776. if (gy) addScaleChannel(channelsByScale, "y", y);
  7777. }
  7778. }
  7779. } else {
  7780. addScaleChannel(channelsByScale, scale, channel);
  7781. }
  7782. }
  7783. }
  7784. }
  7785. return channelsByScale;
  7786. }
  7787. function addScaleChannel(channelsByScale, scale, channel) {
  7788. const scaleChannels = channelsByScale.get(scale);
  7789. if (scaleChannels !== undefined) scaleChannels.push(channel);
  7790. else channelsByScale.set(scale, [channel]);
  7791. }
  7792. // Returns the facet groups, and possibly fx and fy channels, associated with
  7793. // the top-level facet option {data, x, y}.
  7794. function maybeTopFacet(facet, options) {
  7795. if (facet == null) return;
  7796. const {x, y} = facet;
  7797. if (x == null && y == null) return;
  7798. const data = dataify(facet.data);
  7799. if (data == null) throw new Error("missing facet data");
  7800. const channels = {};
  7801. if (x != null) channels.fx = createChannel(data, {value: x, scale: "fx"});
  7802. if (y != null) channels.fy = createChannel(data, {value: y, scale: "fy"});
  7803. applyScaleTransforms(channels, options);
  7804. const groups = facetGroups(data, channels);
  7805. return {channels, groups, data: facet.data};
  7806. }
  7807. // Returns the facet groups, and possibly fx and fy channels, associated with a
  7808. // mark, either through top-level faceting or mark-level facet options {fx, fy}.
  7809. function maybeMarkFacet(mark, topFacetState, options) {
  7810. if (mark.facet === null || mark.facet === "super") return;
  7811. // This mark defines a mark-level facet. TODO There’s some code duplication
  7812. // here with maybeTopFacet that we could reduce.
  7813. const {fx, fy} = mark;
  7814. if (fx != null || fy != null) {
  7815. const data = dataify(mark.data ?? fx ?? fy);
  7816. if (data === undefined) throw new Error(`missing facet data in ${mark.ariaLabel}`);
  7817. if (data === null) return; // ignore channel definitions if no data is provided TODO this right?
  7818. const channels = {};
  7819. if (fx != null) channels.fx = createChannel(data, {value: fx, scale: "fx"});
  7820. if (fy != null) channels.fy = createChannel(data, {value: fy, scale: "fy"});
  7821. applyScaleTransforms(channels, options);
  7822. return {channels, groups: facetGroups(data, channels)};
  7823. }
  7824. // This mark links to a top-level facet, if present.
  7825. if (topFacetState === undefined) return;
  7826. // TODO Can we link the top-level facet channels here?
  7827. const {channels, groups, data} = topFacetState;
  7828. if (mark.facet !== "auto" || mark.data === data) return {channels, groups};
  7829. // Warn for the common pitfall of wanting to facet mapped data with the
  7830. // top-level facet option.
  7831. if (
  7832. data.length > 0 &&
  7833. (groups.size > 1 || (groups.size === 1 && channels.fx && channels.fy && [...groups][0][1].size > 1)) &&
  7834. lengthof(dataify(mark.data)) === lengthof(data)
  7835. ) {
  7836. warn(
  7837. `Warning: the ${mark.ariaLabel} mark appears to use faceted data, but isn’t faceted. The mark data has the same length as the facet data and the mark facet option is "auto", but the mark data and facet data are distinct. If this mark should be faceted, set the mark facet option to true; otherwise, suppress this warning by setting the mark facet option to false.`
  7838. );
  7839. }
  7840. }
  7841. function derive(mark, options = {}) {
  7842. return initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, context) => {
  7843. return context.getMarkState(mark);
  7844. });
  7845. }
  7846. function inferTips(marks) {
  7847. const tips = [];
  7848. for (const mark of marks) {
  7849. let tipOptions = mark.tip;
  7850. if (tipOptions) {
  7851. if (tipOptions === true) tipOptions = {};
  7852. else if (typeof tipOptions === "string") tipOptions = {pointer: tipOptions};
  7853. let {pointer: p, preferredAnchor: a} = tipOptions;
  7854. p = /^x$/i.test(p) ? pointerX : /^y$/i.test(p) ? pointerY : pointer; // TODO validate?
  7855. tipOptions = p(derive(mark, tipOptions));
  7856. tipOptions.title = null; // prevent implicit title for primitive data
  7857. if (a === undefined) tipOptions.preferredAnchor = p === pointerY ? "left" : "bottom";
  7858. const t = tip(mark.data, tipOptions);
  7859. t.facet = mark.facet; // inherit facet settings
  7860. t.facetAnchor = mark.facetAnchor; // inherit facet settings
  7861. tips.push(t);
  7862. }
  7863. }
  7864. return tips;
  7865. }
  7866. function inferAxes(marks, channelsByScale, options) {
  7867. let {
  7868. projection,
  7869. x = {},
  7870. y = {},
  7871. fx = {},
  7872. fy = {},
  7873. axis,
  7874. grid,
  7875. facet = {},
  7876. facet: {axis: facetAxis = axis, grid: facetGrid} = facet,
  7877. x: {axis: xAxis = axis, grid: xGrid = xAxis === null ? null : grid} = x,
  7878. y: {axis: yAxis = axis, grid: yGrid = yAxis === null ? null : grid} = y,
  7879. fx: {axis: fxAxis = facetAxis, grid: fxGrid = fxAxis === null ? null : facetGrid} = fx,
  7880. fy: {axis: fyAxis = facetAxis, grid: fyGrid = fyAxis === null ? null : facetGrid} = fy
  7881. } = options;
  7882. // Disable axes if the corresponding scale is not present.
  7883. if (projection || (!isScaleOptions(x) && !hasPositionChannel("x", marks))) xAxis = xGrid = null;
  7884. if (projection || (!isScaleOptions(y) && !hasPositionChannel("y", marks))) yAxis = yGrid = null;
  7885. if (!channelsByScale.has("fx")) fxAxis = fxGrid = null;
  7886. if (!channelsByScale.has("fy")) fyAxis = fyGrid = null;
  7887. // Resolve the default implicit axes by checking for explicit ones.
  7888. if (xAxis === undefined) xAxis = !hasAxis(marks, "x");
  7889. if (yAxis === undefined) yAxis = !hasAxis(marks, "y");
  7890. if (fxAxis === undefined) fxAxis = !hasAxis(marks, "fx");
  7891. if (fyAxis === undefined) fyAxis = !hasAxis(marks, "fy");
  7892. // Resolve the default orientation of axes.
  7893. if (xAxis === true) xAxis = "bottom";
  7894. if (yAxis === true) yAxis = "left";
  7895. if (fxAxis === true) fxAxis = xAxis === "top" || xAxis === null ? "bottom" : "top";
  7896. if (fyAxis === true) fyAxis = yAxis === "right" || yAxis === null ? "left" : "right";
  7897. const axes = [];
  7898. maybeGrid(axes, fyGrid, gridFy, fy);
  7899. maybeAxis(axes, fyAxis, axisFy, "right", "left", facet, fy);
  7900. maybeGrid(axes, fxGrid, gridFx, fx);
  7901. maybeAxis(axes, fxAxis, axisFx, "top", "bottom", facet, fx);
  7902. maybeGrid(axes, yGrid, gridY, y);
  7903. maybeAxis(axes, yAxis, axisY, "left", "right", options, y);
  7904. maybeGrid(axes, xGrid, gridX, x);
  7905. maybeAxis(axes, xAxis, axisX, "bottom", "top", options, x);
  7906. return axes;
  7907. }
  7908. function maybeAxis(axes, axis, axisType, primary, secondary, defaults, options) {
  7909. if (!axis) return;
  7910. const both = isBoth(axis);
  7911. options = axisOptions(both ? primary : axis, defaults, options);
  7912. const {line} = options;
  7913. if ((axisType === axisY || axisType === axisX) && line && !isNone(line)) axes.push(frame(lineOptions(options)));
  7914. axes.push(axisType(options));
  7915. if (both) axes.push(axisType({...options, anchor: secondary, label: null}));
  7916. }
  7917. function maybeGrid(axes, grid, gridType, options) {
  7918. if (!grid || isNone(grid)) return;
  7919. axes.push(gridType(gridOptions(grid, options)));
  7920. }
  7921. function isBoth(value) {
  7922. return /^\s*both\s*$/i.test(value);
  7923. }
  7924. function axisOptions(
  7925. anchor,
  7926. defaults,
  7927. {
  7928. line = defaults.line,
  7929. ticks,
  7930. tickSize,
  7931. tickSpacing,
  7932. tickPadding,
  7933. tickFormat,
  7934. tickRotate,
  7935. fontVariant,
  7936. ariaLabel,
  7937. ariaDescription,
  7938. label = defaults.label,
  7939. labelAnchor,
  7940. labelArrow = defaults.labelArrow,
  7941. labelOffset
  7942. }
  7943. ) {
  7944. return {
  7945. anchor,
  7946. line,
  7947. ticks,
  7948. tickSize,
  7949. tickSpacing,
  7950. tickPadding,
  7951. tickFormat,
  7952. tickRotate,
  7953. fontVariant,
  7954. ariaLabel,
  7955. ariaDescription,
  7956. label,
  7957. labelAnchor,
  7958. labelArrow,
  7959. labelOffset
  7960. };
  7961. }
  7962. function lineOptions(options) {
  7963. const {anchor, line} = options;
  7964. return {anchor, facetAnchor: anchor + "-empty", stroke: line === true ? undefined : line};
  7965. }
  7966. function gridOptions(
  7967. grid,
  7968. {
  7969. stroke = isColor(grid) ? grid : undefined,
  7970. ticks = isGridTicks(grid) ? grid : undefined,
  7971. tickSpacing,
  7972. ariaLabel,
  7973. ariaDescription
  7974. }
  7975. ) {
  7976. return {
  7977. stroke,
  7978. ticks,
  7979. tickSpacing,
  7980. ariaLabel,
  7981. ariaDescription
  7982. };
  7983. }
  7984. function isGridTicks(grid) {
  7985. switch (typeof grid) {
  7986. case "number":
  7987. return true;
  7988. case "string":
  7989. return !isColor(grid);
  7990. }
  7991. return isIterable(grid) || typeof grid?.range === "function";
  7992. }
  7993. // Is there an explicit axis already present? TODO We probably want a more
  7994. // explicit test than looking for the ARIA label, but it does afford some
  7995. // flexibility in axis implementation which is nice.
  7996. function hasAxis(marks, k) {
  7997. const prefix = `${k}-axis `;
  7998. return marks.some((m) => m.ariaLabel?.startsWith(prefix));
  7999. }
  8000. function hasPositionChannel(k, marks) {
  8001. for (const mark of marks) {
  8002. for (const key in mark.channels) {
  8003. const {scale} = mark.channels[key];
  8004. if (scale === k || scale === "projection") {
  8005. return true;
  8006. }
  8007. }
  8008. }
  8009. return false;
  8010. }
  8011. function inheritScaleLabels(newScales, scales) {
  8012. for (const key in newScales) {
  8013. const newScale = newScales[key];
  8014. const scale = scales[key];
  8015. if (newScale.label === undefined && scale) {
  8016. newScale.label = scale.label;
  8017. }
  8018. }
  8019. return newScales;
  8020. }
  8021. // This differs from the other outerDimensions in that it accounts for rounding
  8022. // and outer padding in the facet scales; we want the frame to align exactly
  8023. // with the actual range, not the desired range.
  8024. function actualDimensions({fx, fy}, dimensions) {
  8025. const {marginTop, marginRight, marginBottom, marginLeft, width, height} = outerDimensions(dimensions);
  8026. const fxr = fx && outerRange(fx);
  8027. const fyr = fy && outerRange(fy);
  8028. return {
  8029. marginTop: fy ? fyr[0] : marginTop,
  8030. marginRight: fx ? width - fxr[1] : marginRight,
  8031. marginBottom: fy ? height - fyr[1] : marginBottom,
  8032. marginLeft: fx ? fxr[0] : marginLeft,
  8033. // Some marks, namely the x- and y-axis labels, want to know what the
  8034. // desired (rather than actual) margins are for positioning.
  8035. inset: {
  8036. marginTop: dimensions.marginTop,
  8037. marginRight: dimensions.marginRight,
  8038. marginBottom: dimensions.marginBottom,
  8039. marginLeft: dimensions.marginLeft
  8040. },
  8041. width,
  8042. height
  8043. };
  8044. }
  8045. function outerRange(scale) {
  8046. const domain = scale.domain();
  8047. if (domain.length === 0) return [0, scale.bandwidth()];
  8048. let x1 = scale(domain[0]);
  8049. let x2 = scale(domain[domain.length - 1]);
  8050. if (x2 < x1) [x1, x2] = [x2, x1];
  8051. return [x1, x2 + scale.bandwidth()];
  8052. }
  8053. const curves = new Map([
  8054. ["basis", d3.curveBasis],
  8055. ["basis-closed", d3.curveBasisClosed],
  8056. ["basis-open", d3.curveBasisOpen],
  8057. ["bundle", d3.curveBundle],
  8058. ["bump-x", d3.curveBumpX],
  8059. ["bump-y", d3.curveBumpY],
  8060. ["cardinal", d3.curveCardinal],
  8061. ["cardinal-closed", d3.curveCardinalClosed],
  8062. ["cardinal-open", d3.curveCardinalOpen],
  8063. ["catmull-rom", d3.curveCatmullRom],
  8064. ["catmull-rom-closed", d3.curveCatmullRomClosed],
  8065. ["catmull-rom-open", d3.curveCatmullRomOpen],
  8066. ["linear", d3.curveLinear],
  8067. ["linear-closed", d3.curveLinearClosed],
  8068. ["monotone-x", d3.curveMonotoneX],
  8069. ["monotone-y", d3.curveMonotoneY],
  8070. ["natural", d3.curveNatural],
  8071. ["step", d3.curveStep],
  8072. ["step-after", d3.curveStepAfter],
  8073. ["step-before", d3.curveStepBefore]
  8074. ]);
  8075. function maybeCurve(curve = d3.curveLinear, tension) {
  8076. if (typeof curve === "function") return curve; // custom curve
  8077. const c = curves.get(`${curve}`.toLowerCase());
  8078. if (!c) throw new Error(`unknown curve: ${curve}`);
  8079. if (tension !== undefined) {
  8080. if ("beta" in c) {
  8081. return c.beta(tension);
  8082. } else if ("tension" in c) {
  8083. return c.tension(tension);
  8084. } else if ("alpha" in c) {
  8085. return c.alpha(tension);
  8086. }
  8087. }
  8088. return c;
  8089. }
  8090. // For the “auto” curve, return a symbol instead of a curve implementation;
  8091. // we’ll use d3.geoPath to render if there’s a projection.
  8092. function maybeCurveAuto(curve = curveAuto, tension) {
  8093. return typeof curve !== "function" && `${curve}`.toLowerCase() === "auto" ? curveAuto : maybeCurve(curve, tension);
  8094. }
  8095. // This is a special built-in curve that will use d3.geoPath when there is a
  8096. // projection, and the linear curve when there is not. You can explicitly
  8097. // opt-out of d3.geoPath and instead use d3.line with the "linear" curve.
  8098. function curveAuto(context) {
  8099. return d3.curveLinear(context);
  8100. }
  8101. // Group on {z, fill, stroke}, then optionally on y, then bin x.
  8102. function binX(outputs = {y: "count"}, options = {}) {
  8103. [outputs, options] = mergeOptions$1(outputs, options);
  8104. const {x, y} = options;
  8105. return binn(maybeBinValue(x, options, identity$1), null, null, y, outputs, maybeInsetX(options));
  8106. }
  8107. // Group on {z, fill, stroke}, then optionally on x, then bin y.
  8108. function binY(outputs = {x: "count"}, options = {}) {
  8109. [outputs, options] = mergeOptions$1(outputs, options);
  8110. const {x, y} = options;
  8111. return binn(null, maybeBinValue(y, options, identity$1), x, null, outputs, maybeInsetY(options));
  8112. }
  8113. // Group on {z, fill, stroke}, then bin on x and y.
  8114. function bin(outputs = {fill: "count"}, options = {}) {
  8115. [outputs, options] = mergeOptions$1(outputs, options);
  8116. const {x, y} = maybeBinValueTuple(options);
  8117. return binn(x, y, null, null, outputs, maybeInsetX(maybeInsetY(options)));
  8118. }
  8119. function maybeDenseInterval(bin, k, options = {}) {
  8120. if (options?.interval == null) return options;
  8121. const {reduce = reduceFirst$1} = options;
  8122. const outputs = {filter: null};
  8123. if (options[k] != null) outputs[k] = reduce;
  8124. if (options[`${k}1`] != null) outputs[`${k}1`] = reduce;
  8125. if (options[`${k}2`] != null) outputs[`${k}2`] = reduce;
  8126. return bin(outputs, options);
  8127. }
  8128. function maybeDenseIntervalX(options = {}) {
  8129. return maybeDenseInterval(binX, "y", withTip(options, "x"));
  8130. }
  8131. function maybeDenseIntervalY(options = {}) {
  8132. return maybeDenseInterval(binY, "x", withTip(options, "y"));
  8133. }
  8134. function binn(
  8135. bx, // optionally bin on x (exclusive with gx)
  8136. by, // optionally bin on y (exclusive with gy)
  8137. gx, // optionally group on x (exclusive with bx and gy)
  8138. gy, // optionally group on y (exclusive with by and gx)
  8139. {
  8140. data: reduceData = reduceIdentity, // TODO avoid materializing when unused?
  8141. filter = reduceCount, // return only non-empty bins by default
  8142. sort,
  8143. reverse,
  8144. ...outputs // output channel definitions
  8145. } = {},
  8146. inputs = {} // input channels and options
  8147. ) {
  8148. bx = maybeBin(bx);
  8149. by = maybeBin(by);
  8150. // Compute the outputs.
  8151. outputs = maybeBinOutputs(outputs, inputs);
  8152. reduceData = maybeBinReduce(reduceData, identity$1);
  8153. sort = sort == null ? undefined : maybeBinOutput("sort", sort, inputs);
  8154. filter = filter == null ? undefined : maybeBinEvaluator("filter", filter, inputs);
  8155. // Don’t group on a channel if an output requires it as an input!
  8156. if (gx != null && hasOutput(outputs, "x", "x1", "x2")) gx = null;
  8157. if (gy != null && hasOutput(outputs, "y", "y1", "y2")) gy = null;
  8158. // Produce x1, x2, y1, and y2 output channels as appropriate (when binning).
  8159. const [BX1, setBX1] = maybeColumn(bx);
  8160. const [BX2, setBX2] = maybeColumn(bx);
  8161. const [BY1, setBY1] = maybeColumn(by);
  8162. const [BY2, setBY2] = maybeColumn(by);
  8163. // Produce x or y output channels as appropriate (when grouping).
  8164. const [k, gk] = gx != null ? [gx, "x"] : gy != null ? [gy, "y"] : [];
  8165. const [GK, setGK] = maybeColumn(k);
  8166. // Greedily materialize the z, fill, and stroke channels (if channels and not
  8167. // constants) so that we can reference them for subdividing groups without
  8168. // computing them more than once. We also want to consume options that should
  8169. // only apply to this transform rather than passing them through to the next.
  8170. const {
  8171. x,
  8172. y,
  8173. z,
  8174. fill,
  8175. stroke,
  8176. x1,
  8177. x2, // consumed if x is an output
  8178. y1,
  8179. y2, // consumed if y is an output
  8180. domain,
  8181. cumulative,
  8182. thresholds,
  8183. interval,
  8184. ...options
  8185. } = inputs;
  8186. const [GZ, setGZ] = maybeColumn(z);
  8187. const [vfill] = maybeColorChannel(fill);
  8188. const [vstroke] = maybeColorChannel(stroke);
  8189. const [GF, setGF] = maybeColumn(vfill);
  8190. const [GS, setGS] = maybeColumn(vstroke);
  8191. return {
  8192. ...("z" in inputs && {z: GZ || z}),
  8193. ...("fill" in inputs && {fill: GF || fill}),
  8194. ...("stroke" in inputs && {stroke: GS || stroke}),
  8195. ...basic(options, (data, facets, plotOptions) => {
  8196. const K = maybeApplyInterval(valueof(data, k), plotOptions?.[gk]);
  8197. const Z = valueof(data, z);
  8198. const F = valueof(data, vfill);
  8199. const S = valueof(data, vstroke);
  8200. const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S});
  8201. const groupFacets = [];
  8202. const groupData = [];
  8203. const GK = K && setGK([]);
  8204. const GZ = Z && setGZ([]);
  8205. const GF = F && setGF([]);
  8206. const GS = S && setGS([]);
  8207. const BX1 = bx && setBX1([]);
  8208. const BX2 = bx && setBX2([]);
  8209. const BY1 = by && setBY1([]);
  8210. const BY2 = by && setBY2([]);
  8211. const bin = bing(bx, by, data);
  8212. let i = 0;
  8213. for (const o of outputs) o.initialize(data);
  8214. if (sort) sort.initialize(data);
  8215. if (filter) filter.initialize(data);
  8216. for (const facet of facets) {
  8217. const groupFacet = [];
  8218. for (const o of outputs) o.scope("facet", facet);
  8219. if (sort) sort.scope("facet", facet);
  8220. if (filter) filter.scope("facet", facet);
  8221. for (const [f, I] of maybeGroup(facet, G)) {
  8222. for (const [k, g] of maybeGroup(I, K)) {
  8223. for (const [b, extent] of bin(g)) {
  8224. if (G) extent.z = f;
  8225. if (filter && !filter.reduce(b, extent)) continue;
  8226. groupFacet.push(i++);
  8227. groupData.push(reduceData.reduceIndex(b, data, extent));
  8228. if (K) GK.push(k);
  8229. if (Z) GZ.push(G === Z ? f : Z[(b.length > 0 ? b : g)[0]]);
  8230. if (F) GF.push(G === F ? f : F[(b.length > 0 ? b : g)[0]]);
  8231. if (S) GS.push(G === S ? f : S[(b.length > 0 ? b : g)[0]]);
  8232. if (BX1) BX1.push(extent.x1), BX2.push(extent.x2);
  8233. if (BY1) BY1.push(extent.y1), BY2.push(extent.y2);
  8234. for (const o of outputs) o.reduce(b, extent);
  8235. if (sort) sort.reduce(b, extent);
  8236. }
  8237. }
  8238. }
  8239. groupFacets.push(groupFacet);
  8240. }
  8241. maybeSort(groupFacets, sort, reverse);
  8242. return {data: groupData, facets: groupFacets};
  8243. }),
  8244. ...(!hasOutput(outputs, "x") && (BX1 ? {x1: BX1, x2: BX2, x: mid(BX1, BX2)} : {x, x1, x2})),
  8245. ...(!hasOutput(outputs, "y") && (BY1 ? {y1: BY1, y2: BY2, y: mid(BY1, BY2)} : {y, y1, y2})),
  8246. ...(GK && {[gk]: GK}),
  8247. ...Object.fromEntries(outputs.map(({name, output}) => [name, output]))
  8248. };
  8249. }
  8250. // Allow bin options to be specified as part of outputs; merge them into options.
  8251. function mergeOptions$1({cumulative, domain, thresholds, interval, ...outputs}, options) {
  8252. return [outputs, {cumulative, domain, thresholds, interval, ...options}];
  8253. }
  8254. function maybeBinValue(value, {cumulative, domain, thresholds, interval}, defaultValue) {
  8255. value = {...maybeValue(value)};
  8256. if (value.domain === undefined) value.domain = domain;
  8257. if (value.cumulative === undefined) value.cumulative = cumulative;
  8258. if (value.thresholds === undefined) value.thresholds = thresholds;
  8259. if (value.interval === undefined) value.interval = interval;
  8260. if (value.value === undefined) value.value = defaultValue;
  8261. value.thresholds = maybeThresholds(value.thresholds, value.interval);
  8262. return value;
  8263. }
  8264. function maybeBinValueTuple(options) {
  8265. let {x, y} = options;
  8266. x = maybeBinValue(x, options);
  8267. y = maybeBinValue(y, options);
  8268. [x.value, y.value] = maybeTuple(x.value, y.value);
  8269. return {x, y};
  8270. }
  8271. function maybeBin(options) {
  8272. if (options == null) return;
  8273. const {value, cumulative, domain = d3.extent, thresholds} = options;
  8274. const bin = (data) => {
  8275. let V = valueof(data, value);
  8276. let T; // bin thresholds
  8277. if (isTemporal(V) || isTimeThresholds(thresholds)) {
  8278. V = map$1(V, coerceDate, Float64Array); // like coerceDates, but faster
  8279. let [min, max] = typeof domain === "function" ? domain(V) : domain;
  8280. let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
  8281. if (typeof t === "number") t = d3.utcTickInterval(min, max, t);
  8282. if (isInterval(t)) {
  8283. if (domain === d3.extent) {
  8284. min = t.floor(min);
  8285. max = t.offset(t.floor(max));
  8286. }
  8287. t = t.range(min, t.offset(max));
  8288. }
  8289. T = t;
  8290. } else {
  8291. V = coerceNumbers(V);
  8292. let [min, max] = typeof domain === "function" ? domain(V) : domain;
  8293. let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds;
  8294. if (typeof t === "number") {
  8295. // This differs from d3.ticks with regard to exclusive bounds: we want a
  8296. // first threshold less than or equal to the minimum, and a last
  8297. // threshold (strictly) greater than the maximum.
  8298. if (domain === d3.extent) {
  8299. let step = d3.tickIncrement(min, max, t);
  8300. if (isFinite(step)) {
  8301. if (step > 0) {
  8302. let r0 = Math.round(min / step);
  8303. let r1 = Math.round(max / step);
  8304. if (!(r0 * step <= min)) --r0;
  8305. if (!(r1 * step > max)) ++r1;
  8306. let n = r1 - r0 + 1;
  8307. t = new Float64Array(n);
  8308. for (let i = 0; i < n; ++i) t[i] = (r0 + i) * step;
  8309. } else if (step < 0) {
  8310. step = -step;
  8311. let r0 = Math.round(min * step);
  8312. let r1 = Math.round(max * step);
  8313. if (!(r0 / step <= min)) --r0;
  8314. if (!(r1 / step > max)) ++r1;
  8315. let n = r1 - r0 + 1;
  8316. t = new Float64Array(n);
  8317. for (let i = 0; i < n; ++i) t[i] = (r0 + i) / step;
  8318. } else {
  8319. t = [min];
  8320. }
  8321. } else {
  8322. t = [min];
  8323. }
  8324. } else {
  8325. t = d3.ticks(min, max, t);
  8326. }
  8327. } else if (isInterval(t)) {
  8328. if (domain === d3.extent) {
  8329. min = t.floor(min);
  8330. max = t.offset(t.floor(max));
  8331. }
  8332. t = t.range(min, t.offset(max));
  8333. }
  8334. T = t;
  8335. }
  8336. const E = [];
  8337. if (T.length === 1) E.push([T[0], T[0]]); // collapsed domain
  8338. else for (let i = 1; i < T.length; ++i) E.push([T[i - 1], T[i]]);
  8339. E.bin = (cumulative < 0 ? bin1cn : cumulative > 0 ? bin1cp : bin1)(E, T, V);
  8340. return E;
  8341. };
  8342. bin.label = labelof(value);
  8343. return bin;
  8344. }
  8345. function maybeThresholds(thresholds, interval, defaultThresholds = thresholdAuto) {
  8346. if (thresholds === undefined) {
  8347. return interval === undefined ? defaultThresholds : maybeRangeInterval(interval);
  8348. }
  8349. if (typeof thresholds === "string") {
  8350. switch (thresholds.toLowerCase()) {
  8351. case "freedman-diaconis":
  8352. return d3.thresholdFreedmanDiaconis;
  8353. case "scott":
  8354. return d3.thresholdScott;
  8355. case "sturges":
  8356. return d3.thresholdSturges;
  8357. case "auto":
  8358. return thresholdAuto;
  8359. }
  8360. return utcInterval(thresholds);
  8361. }
  8362. return thresholds; // pass array, count, or function to bin.thresholds
  8363. }
  8364. function maybeBinOutputs(outputs, inputs) {
  8365. return maybeOutputs(outputs, inputs, maybeBinOutput);
  8366. }
  8367. function maybeBinOutput(name, reduce, inputs) {
  8368. return maybeOutput(name, reduce, inputs, maybeBinEvaluator);
  8369. }
  8370. function maybeBinEvaluator(name, reduce, inputs) {
  8371. return maybeEvaluator(name, reduce, inputs, maybeBinReduce);
  8372. }
  8373. function maybeBinReduce(reduce, value) {
  8374. return maybeReduce$1(reduce, value, maybeBinReduceFallback);
  8375. }
  8376. function maybeBinReduceFallback(reduce) {
  8377. switch (`${reduce}`.toLowerCase()) {
  8378. case "x":
  8379. return reduceX;
  8380. case "x1":
  8381. return reduceX1;
  8382. case "x2":
  8383. return reduceX2;
  8384. case "y":
  8385. return reduceY;
  8386. case "y1":
  8387. return reduceY1;
  8388. case "y2":
  8389. return reduceY2;
  8390. case "z":
  8391. return reduceZ;
  8392. }
  8393. throw new Error(`invalid bin reduce: ${reduce}`);
  8394. }
  8395. function thresholdAuto(values, min, max) {
  8396. return Math.min(200, d3.thresholdScott(values, min, max));
  8397. }
  8398. function isTimeThresholds(t) {
  8399. return isTimeInterval(t) || (isIterable(t) && isTemporal(t));
  8400. }
  8401. function bing(bx, by, data) {
  8402. const EX = bx?.(data);
  8403. const EY = by?.(data);
  8404. return EX && EY
  8405. ? function* (I) {
  8406. const X = EX.bin(I); // first bin on x
  8407. for (const [ix, [x1, x2]] of EX.entries()) {
  8408. const Y = EY.bin(X[ix]); // then bin on y
  8409. for (const [iy, [y1, y2]] of EY.entries()) {
  8410. yield [Y[iy], {data, x1, y1, x2, y2}];
  8411. }
  8412. }
  8413. }
  8414. : EX
  8415. ? function* (I) {
  8416. const X = EX.bin(I);
  8417. for (const [i, [x1, x2]] of EX.entries()) {
  8418. yield [X[i], {data, x1, x2}];
  8419. }
  8420. }
  8421. : function* (I) {
  8422. const Y = EY.bin(I);
  8423. for (const [i, [y1, y2]] of EY.entries()) {
  8424. yield [Y[i], {data, y1, y2}];
  8425. }
  8426. };
  8427. }
  8428. // non-cumulative distribution
  8429. function bin1(E, T, V) {
  8430. T = coerceNumbers(T); // for faster bisection
  8431. return (I) => {
  8432. const B = E.map(() => []);
  8433. for (const i of I) B[d3.bisect(T, V[i]) - 1]?.push(i); // TODO quantization?
  8434. return B;
  8435. };
  8436. }
  8437. // cumulative distribution
  8438. function bin1cp(E, T, V) {
  8439. const bin = bin1(E, T, V);
  8440. return (I) => {
  8441. const B = bin(I);
  8442. for (let i = 1, n = B.length; i < n; ++i) {
  8443. const C = B[i - 1];
  8444. const b = B[i];
  8445. for (const j of C) b.push(j);
  8446. }
  8447. return B;
  8448. };
  8449. }
  8450. // complementary cumulative distribution
  8451. function bin1cn(E, T, V) {
  8452. const bin = bin1(E, T, V);
  8453. return (I) => {
  8454. const B = bin(I);
  8455. for (let i = B.length - 2; i >= 0; --i) {
  8456. const C = B[i + 1];
  8457. const b = B[i];
  8458. for (const j of C) b.push(j);
  8459. }
  8460. return B;
  8461. };
  8462. }
  8463. function mid1(x1, x2) {
  8464. const m = (+x1 + +x2) / 2;
  8465. return x1 instanceof Date ? new Date(m) : m;
  8466. }
  8467. const reduceX = {
  8468. reduceIndex(I, X, {x1, x2}) {
  8469. return mid1(x1, x2);
  8470. }
  8471. };
  8472. const reduceY = {
  8473. reduceIndex(I, X, {y1, y2}) {
  8474. return mid1(y1, y2);
  8475. }
  8476. };
  8477. const reduceX1 = {
  8478. reduceIndex(I, X, {x1}) {
  8479. return x1;
  8480. }
  8481. };
  8482. const reduceX2 = {
  8483. reduceIndex(I, X, {x2}) {
  8484. return x2;
  8485. }
  8486. };
  8487. const reduceY1 = {
  8488. reduceIndex(I, X, {y1}) {
  8489. return y1;
  8490. }
  8491. };
  8492. const reduceY2 = {
  8493. reduceIndex(I, X, {y2}) {
  8494. return y2;
  8495. }
  8496. };
  8497. const defaults$e = {
  8498. ariaLabel: "area",
  8499. strokeWidth: 1,
  8500. strokeLinecap: "round",
  8501. strokeLinejoin: "round",
  8502. strokeMiterlimit: 1
  8503. };
  8504. class Area extends Mark {
  8505. constructor(data, options = {}) {
  8506. const {x1, y1, x2, y2, z, curve, tension} = options;
  8507. super(
  8508. data,
  8509. {
  8510. x1: {value: x1, scale: "x"},
  8511. y1: {value: y1, scale: "y"},
  8512. x2: {value: x2, scale: "x", optional: true},
  8513. y2: {value: y2, scale: "y", optional: true},
  8514. z: {value: maybeZ(options), optional: true}
  8515. },
  8516. options,
  8517. defaults$e
  8518. );
  8519. this.z = z;
  8520. this.curve = maybeCurve(curve, tension);
  8521. }
  8522. filter(index) {
  8523. return index;
  8524. }
  8525. render(index, scales, channels, dimensions, context) {
  8526. const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
  8527. return create("svg:g", context)
  8528. .call(applyIndirectStyles, this, dimensions, context)
  8529. .call(applyTransform, this, scales, 0, 0)
  8530. .call((g) =>
  8531. g
  8532. .selectAll()
  8533. .data(groupIndex(index, [X1, Y1, X2, Y2], this, channels))
  8534. .enter()
  8535. .append("path")
  8536. .call(applyDirectStyles, this)
  8537. .call(applyGroupedChannelStyles, this, channels)
  8538. .attr(
  8539. "d",
  8540. d3.area()
  8541. .curve(this.curve)
  8542. .defined((i) => i >= 0)
  8543. .x0((i) => X1[i])
  8544. .y0((i) => Y1[i])
  8545. .x1((i) => X2[i])
  8546. .y1((i) => Y2[i])
  8547. )
  8548. )
  8549. .node();
  8550. }
  8551. }
  8552. function area(data, options) {
  8553. if (options === undefined) return areaY(data, {x: first, y: second});
  8554. return new Area(data, options);
  8555. }
  8556. function areaX(data, options) {
  8557. const {y = indexOf, ...rest} = maybeDenseIntervalY(options);
  8558. return new Area(data, maybeStackX(maybeIdentityX({...rest, y1: y, y2: undefined}, y === indexOf ? "x2" : "x")));
  8559. }
  8560. function areaY(data, options) {
  8561. const {x = indexOf, ...rest} = maybeDenseIntervalX(options);
  8562. return new Area(data, maybeStackY(maybeIdentityY({...rest, x1: x, x2: undefined}, x === indexOf ? "y2" : "y")));
  8563. }
  8564. const defaults$d = {
  8565. ariaLabel: "link",
  8566. fill: "none",
  8567. stroke: "currentColor",
  8568. strokeMiterlimit: 1
  8569. };
  8570. class Link extends Mark {
  8571. constructor(data, options = {}) {
  8572. const {x1, y1, x2, y2, curve, tension} = options;
  8573. super(
  8574. data,
  8575. {
  8576. x1: {value: x1, scale: "x"},
  8577. y1: {value: y1, scale: "y"},
  8578. x2: {value: x2, scale: "x", optional: true},
  8579. y2: {value: y2, scale: "y", optional: true}
  8580. },
  8581. options,
  8582. defaults$d
  8583. );
  8584. this.curve = maybeCurveAuto(curve, tension);
  8585. markers(this, options);
  8586. }
  8587. project(channels, values, context) {
  8588. // For the auto curve, projection is handled at render.
  8589. if (this.curve !== curveAuto) {
  8590. super.project(channels, values, context);
  8591. }
  8592. }
  8593. render(index, scales, channels, dimensions, context) {
  8594. const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels;
  8595. const {curve} = this;
  8596. return create("svg:g", context)
  8597. .call(applyIndirectStyles, this, dimensions, context)
  8598. .call(applyTransform, this, scales)
  8599. .call((g) =>
  8600. g
  8601. .selectAll()
  8602. .data(index)
  8603. .enter()
  8604. .append("path")
  8605. .call(applyDirectStyles, this)
  8606. .attr(
  8607. "d",
  8608. curve === curveAuto && context.projection
  8609. ? sphereLink(context.path(), X1, Y1, X2, Y2)
  8610. : (i) => {
  8611. const p = d3.pathRound();
  8612. const c = curve(p);
  8613. c.lineStart();
  8614. c.point(X1[i], Y1[i]);
  8615. c.point(X2[i], Y2[i]);
  8616. c.lineEnd();
  8617. return p;
  8618. }
  8619. )
  8620. .call(applyChannelStyles, this, channels)
  8621. .call(applyMarkers, this, channels, context)
  8622. )
  8623. .node();
  8624. }
  8625. }
  8626. function sphereLink(path, X1, Y1, X2, Y2) {
  8627. X1 = coerceNumbers(X1);
  8628. Y1 = coerceNumbers(Y1);
  8629. X2 = coerceNumbers(X2);
  8630. Y2 = coerceNumbers(Y2);
  8631. return (i) =>
  8632. path({
  8633. type: "LineString",
  8634. coordinates: [
  8635. [X1[i], Y1[i]],
  8636. [X2[i], Y2[i]]
  8637. ]
  8638. });
  8639. }
  8640. function link(data, {x, x1, x2, y, y1, y2, ...options} = {}) {
  8641. [x1, x2] = maybeSameValue(x, x1, x2);
  8642. [y1, y2] = maybeSameValue(y, y1, y2);
  8643. return new Link(data, {...options, x1, x2, y1, y2});
  8644. }
  8645. // If x1 and x2 are specified, return them as {x1, x2}.
  8646. // If x and x1 and specified, or x and x2 are specified, return them as {x1, x2}.
  8647. // If only x, x1, or x2 are specified, return it as {x1}.
  8648. function maybeSameValue(x, x1, x2) {
  8649. if (x === undefined) {
  8650. if (x1 === undefined) {
  8651. if (x2 !== undefined) return [x2];
  8652. } else {
  8653. if (x2 === undefined) return [x1];
  8654. }
  8655. } else if (x1 === undefined) {
  8656. return x2 === undefined ? [x] : [x, x2];
  8657. } else if (x2 === undefined) {
  8658. return [x, x1];
  8659. }
  8660. return [x1, x2];
  8661. }
  8662. const defaults$c = {
  8663. ariaLabel: "arrow",
  8664. fill: "none",
  8665. stroke: "currentColor",
  8666. strokeLinecap: "round",
  8667. strokeMiterlimit: 1,
  8668. strokeWidth: 1.5
  8669. };
  8670. class Arrow extends Mark {
  8671. constructor(data, options = {}) {
  8672. const {
  8673. x1,
  8674. y1,
  8675. x2,
  8676. y2,
  8677. bend = 0,
  8678. headAngle = 60,
  8679. headLength = 8, // Disable the arrow with headLength = 0; or, use Plot.link.
  8680. inset = 0,
  8681. insetStart = inset,
  8682. insetEnd = inset,
  8683. sweep
  8684. } = options;
  8685. super(
  8686. data,
  8687. {
  8688. x1: {value: x1, scale: "x"},
  8689. y1: {value: y1, scale: "y"},
  8690. x2: {value: x2, scale: "x", optional: true},
  8691. y2: {value: y2, scale: "y", optional: true}
  8692. },
  8693. options,
  8694. defaults$c
  8695. );
  8696. this.bend = bend === true ? 22.5 : Math.max(-90, Math.min(90, bend));
  8697. this.headAngle = +headAngle;
  8698. this.headLength = +headLength;
  8699. this.insetStart = +insetStart;
  8700. this.insetEnd = +insetEnd;
  8701. this.sweep = maybeSweep(sweep);
  8702. }
  8703. render(index, scales, channels, dimensions, context) {
  8704. const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, SW} = channels;
  8705. const {strokeWidth, bend, headAngle, headLength, insetStart, insetEnd} = this;
  8706. const sw = SW ? (i) => SW[i] : constant(strokeWidth === undefined ? 1 : strokeWidth);
  8707. // The angle between the arrow’s shaft and one of the wings; the “head”
  8708. // angle between the wings is twice this value.
  8709. const wingAngle = (headAngle * radians) / 2;
  8710. // The length of the arrowhead’s “wings” (the line segments that extend from
  8711. // the end point) relative to the stroke width.
  8712. const wingScale = headLength / 1.5;
  8713. return create("svg:g", context)
  8714. .call(applyIndirectStyles, this, dimensions, context)
  8715. .call(applyTransform, this, scales)
  8716. .call((g) =>
  8717. g
  8718. .selectAll()
  8719. .data(index)
  8720. .enter()
  8721. .append("path")
  8722. .call(applyDirectStyles, this)
  8723. .attr("d", (i) => {
  8724. // The start ⟨x1,y1⟩ and end ⟨x2,y2⟩ points may be inset, and the
  8725. // ending line angle may be altered for inset swoopy arrows.
  8726. let x1 = X1[i],
  8727. y1 = Y1[i],
  8728. x2 = X2[i],
  8729. y2 = Y2[i];
  8730. const lineLength = Math.hypot(x2 - x1, y2 - y1);
  8731. if (lineLength <= insetStart + insetEnd) return null;
  8732. let lineAngle = Math.atan2(y2 - y1, x2 - x1);
  8733. // We don’t allow the wing length to be too large relative to the
  8734. // length of the arrow. (Plot.vector allows arbitrarily large
  8735. // wings, but that’s okay since vectors are usually small.)
  8736. const headLength = Math.min(wingScale * sw(i), lineLength / 3);
  8737. // When bending, the offset between the straight line between the two points
  8738. // and the outgoing tangent from the start point. (Also the negative
  8739. // incoming tangent to the end point.) This must be within ±π/2. A positive
  8740. // angle will produce a clockwise curve; a negative angle will produce a
  8741. // counterclockwise curve; zero will produce a straight line.
  8742. const bendAngle = this.sweep(x1, y1, x2, y2) * bend * radians;
  8743. // The radius of the circle that intersects with the two endpoints
  8744. // and has the specified bend angle.
  8745. const r = Math.hypot(lineLength / Math.tan(bendAngle), lineLength) / 2;
  8746. // Apply insets.
  8747. if (insetStart || insetEnd) {
  8748. if (r < 1e5) {
  8749. // For inset swoopy arrows, compute the circle-circle
  8750. // intersection between a circle centered around the
  8751. // respective arrow endpoint and the center of the circle
  8752. // segment that forms the shaft of the arrow.
  8753. const sign = Math.sign(bendAngle);
  8754. const [cx, cy] = pointPointCenter([x1, y1], [x2, y2], r, sign);
  8755. if (insetStart) {
  8756. [x1, y1] = circleCircleIntersect([cx, cy, r], [x1, y1, insetStart], -sign * Math.sign(insetStart));
  8757. }
  8758. // For the end inset, rotate the arrowhead so that it aligns
  8759. // with the truncated end of the arrow. Since the arrow is a
  8760. // segment of the circle centered at ⟨cx,cy⟩, we can compute
  8761. // the angular difference to the new endpoint.
  8762. if (insetEnd) {
  8763. const [x, y] = circleCircleIntersect([cx, cy, r], [x2, y2, insetEnd], sign * Math.sign(insetEnd));
  8764. lineAngle += Math.atan2(y - cy, x - cx) - Math.atan2(y2 - cy, x2 - cx);
  8765. (x2 = x), (y2 = y);
  8766. }
  8767. } else {
  8768. // For inset straight arrows, offset along the straight line.
  8769. const dx = x2 - x1,
  8770. dy = y2 - y1,
  8771. d = Math.hypot(dx, dy);
  8772. if (insetStart) (x1 += (dx / d) * insetStart), (y1 += (dy / d) * insetStart);
  8773. if (insetEnd) (x2 -= (dx / d) * insetEnd), (y2 -= (dy / d) * insetEnd);
  8774. }
  8775. }
  8776. // The angle of the arrow as it approaches the endpoint, and the
  8777. // angles of the adjacent wings. Here “left” refers to if the
  8778. // arrow is pointing up.
  8779. const endAngle = lineAngle + bendAngle;
  8780. const leftAngle = endAngle + wingAngle;
  8781. const rightAngle = endAngle - wingAngle;
  8782. // The endpoints of the two wings.
  8783. const x3 = x2 - headLength * Math.cos(leftAngle);
  8784. const y3 = y2 - headLength * Math.sin(leftAngle);
  8785. const x4 = x2 - headLength * Math.cos(rightAngle);
  8786. const y4 = y2 - headLength * Math.sin(rightAngle);
  8787. // If the radius is very large (or even infinite, as when the bend
  8788. // angle is zero), then render a straight line.
  8789. const a = r < 1e5 ? `A${r},${r} 0,0,${bendAngle > 0 ? 1 : 0} ` : `L`;
  8790. const h = headLength ? `M${x3},${y3}L${x2},${y2}L${x4},${y4}` : "";
  8791. return `M${x1},${y1}${a}${x2},${y2}${h}`;
  8792. })
  8793. .call(applyChannelStyles, this, channels)
  8794. )
  8795. .node();
  8796. }
  8797. }
  8798. // Maybe flip the bend angle, depending on the arrow orientation.
  8799. function maybeSweep(sweep = 1) {
  8800. if (typeof sweep === "number") return constant(Math.sign(sweep));
  8801. if (typeof sweep === "function") return (x1, y1, x2, y2) => Math.sign(sweep(x1, y1, x2, y2));
  8802. switch (keyword(sweep, "sweep", ["+x", "-x", "+y", "-y"])) {
  8803. case "+x":
  8804. return (x1, y1, x2) => d3.ascending(x1, x2);
  8805. case "-x":
  8806. return (x1, y1, x2) => d3.descending(x1, x2);
  8807. case "+y":
  8808. return (x1, y1, x2, y2) => d3.ascending(y1, y2);
  8809. case "-y":
  8810. return (x1, y1, x2, y2) => d3.descending(y1, y2);
  8811. }
  8812. }
  8813. // Returns the center of a circle that goes through the two given points ⟨ax,ay⟩
  8814. // and ⟨bx,by⟩ and has radius r. There are two such points; use the sign +1 or
  8815. // -1 to choose between them. Returns [NaN, NaN] if r is too small.
  8816. function pointPointCenter([ax, ay], [bx, by], r, sign) {
  8817. const dx = bx - ax,
  8818. dy = by - ay,
  8819. d = Math.hypot(dx, dy);
  8820. const k = (sign * Math.sqrt(r * r - (d * d) / 4)) / d;
  8821. return [(ax + bx) / 2 - dy * k, (ay + by) / 2 + dx * k];
  8822. }
  8823. // Given two circles, one centered at ⟨ax,ay⟩ with radius ar, and the other
  8824. // centered at ⟨bx,by⟩ with radius br, returns a point at which the two circles
  8825. // intersect. There are typically two such points; use the sign +1 or -1 to
  8826. // chose between them. Returns [NaN, NaN] if there is no intersection.
  8827. // https://mathworld.wolfram.com/Circle-CircleIntersection.html
  8828. function circleCircleIntersect([ax, ay, ar], [bx, by, br], sign) {
  8829. const dx = bx - ax,
  8830. dy = by - ay,
  8831. d = Math.hypot(dx, dy);
  8832. const x = (dx * dx + dy * dy - br * br + ar * ar) / (2 * d);
  8833. const y = sign * Math.sqrt(ar * ar - x * x);
  8834. return [ax + (dx * x + dy * y) / d, ay + (dy * x - dx * y) / d];
  8835. }
  8836. function arrow(data, {x, x1, x2, y, y1, y2, ...options} = {}) {
  8837. [x1, x2] = maybeSameValue(x, x1, x2);
  8838. [y1, y2] = maybeSameValue(y, y1, y2);
  8839. return new Arrow(data, {...options, x1, x2, y1, y2});
  8840. }
  8841. const barDefaults = {
  8842. ariaLabel: "bar"
  8843. };
  8844. class AbstractBar extends Mark {
  8845. constructor(data, channels, options = {}, defaults = barDefaults) {
  8846. super(data, channels, options, defaults);
  8847. rectInsets(this, options);
  8848. rectRadii(this, options);
  8849. }
  8850. render(index, scales, channels, dimensions, context) {
  8851. const {rx, ry, rx1y1, rx1y2, rx2y1, rx2y2} = this;
  8852. const x = this._x(scales, channels, dimensions);
  8853. const y = this._y(scales, channels, dimensions);
  8854. const w = this._width(scales, channels, dimensions);
  8855. const h = this._height(scales, channels, dimensions);
  8856. return create("svg:g", context)
  8857. .call(applyIndirectStyles, this, dimensions, context)
  8858. .call(this._transform, this, scales)
  8859. .call((g) =>
  8860. g
  8861. .selectAll()
  8862. .data(index)
  8863. .enter()
  8864. .call(
  8865. rx1y1 || rx1y2 || rx2y1 || rx2y2
  8866. ? (g) =>
  8867. g
  8868. .append("path")
  8869. .call(applyDirectStyles, this)
  8870. .call(applyRoundedRect, x, y, add(x, w), add(y, h), this)
  8871. .call(applyChannelStyles, this, channels)
  8872. : (g) =>
  8873. g
  8874. .append("rect")
  8875. .call(applyDirectStyles, this)
  8876. .attr("x", x)
  8877. .attr("width", w)
  8878. .attr("y", y)
  8879. .attr("height", h)
  8880. .call(applyAttr, "rx", rx)
  8881. .call(applyAttr, "ry", ry)
  8882. .call(applyChannelStyles, this, channels)
  8883. )
  8884. )
  8885. .node();
  8886. }
  8887. _x(scales, {x: X}, {marginLeft}) {
  8888. const {insetLeft} = this;
  8889. return X ? (i) => X[i] + insetLeft : marginLeft + insetLeft;
  8890. }
  8891. _y(scales, {y: Y}, {marginTop}) {
  8892. const {insetTop} = this;
  8893. return Y ? (i) => Y[i] + insetTop : marginTop + insetTop;
  8894. }
  8895. _width({x}, {x: X}, {marginRight, marginLeft, width}) {
  8896. const {insetLeft, insetRight} = this;
  8897. const bandwidth = X && x ? x.bandwidth() : width - marginRight - marginLeft;
  8898. return Math.max(0, bandwidth - insetLeft - insetRight);
  8899. }
  8900. _height({y}, {y: Y}, {marginTop, marginBottom, height}) {
  8901. const {insetTop, insetBottom} = this;
  8902. const bandwidth = Y && y ? y.bandwidth() : height - marginTop - marginBottom;
  8903. return Math.max(0, bandwidth - insetTop - insetBottom);
  8904. }
  8905. }
  8906. function add(a, b) {
  8907. return typeof a === "function" && typeof b === "function"
  8908. ? (i) => a(i) + b(i)
  8909. : typeof a === "function"
  8910. ? (i) => a(i) + b
  8911. : typeof b === "function"
  8912. ? (i) => a + b(i)
  8913. : a + b;
  8914. }
  8915. class BarX extends AbstractBar {
  8916. constructor(data, options = {}, defaults) {
  8917. const {x1, x2, y} = options;
  8918. super(
  8919. data,
  8920. {
  8921. x1: {value: x1, scale: "x"},
  8922. x2: {value: x2, scale: "x"},
  8923. y: {value: y, scale: "y", type: "band", optional: true}
  8924. },
  8925. options,
  8926. defaults
  8927. );
  8928. }
  8929. _transform(selection, mark, {x}) {
  8930. selection.call(applyTransform, mark, {x}, 0, 0);
  8931. }
  8932. _x({x}, {x1: X1, x2: X2}, {marginLeft}) {
  8933. const {insetLeft} = this;
  8934. return isCollapsed(x) ? marginLeft + insetLeft : (i) => Math.min(X1[i], X2[i]) + insetLeft;
  8935. }
  8936. _width({x}, {x1: X1, x2: X2}, {marginRight, marginLeft, width}) {
  8937. const {insetLeft, insetRight} = this;
  8938. return isCollapsed(x)
  8939. ? width - marginRight - marginLeft - insetLeft - insetRight
  8940. : (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight);
  8941. }
  8942. }
  8943. class BarY extends AbstractBar {
  8944. constructor(data, options = {}, defaults) {
  8945. const {x, y1, y2} = options;
  8946. super(
  8947. data,
  8948. {
  8949. y1: {value: y1, scale: "y"},
  8950. y2: {value: y2, scale: "y"},
  8951. x: {value: x, scale: "x", type: "band", optional: true}
  8952. },
  8953. options,
  8954. defaults
  8955. );
  8956. }
  8957. _transform(selection, mark, {y}) {
  8958. selection.call(applyTransform, mark, {y}, 0, 0);
  8959. }
  8960. _y({y}, {y1: Y1, y2: Y2}, {marginTop}) {
  8961. const {insetTop} = this;
  8962. return isCollapsed(y) ? marginTop + insetTop : (i) => Math.min(Y1[i], Y2[i]) + insetTop;
  8963. }
  8964. _height({y}, {y1: Y1, y2: Y2}, {marginTop, marginBottom, height}) {
  8965. const {insetTop, insetBottom} = this;
  8966. return isCollapsed(y)
  8967. ? height - marginTop - marginBottom - insetTop - insetBottom
  8968. : (i) => Math.max(0, Math.abs(Y2[i] - Y1[i]) - insetTop - insetBottom);
  8969. }
  8970. }
  8971. function barX(data, options = {}) {
  8972. if (!hasXY(options)) options = {...options, y: indexOf, x2: identity$1};
  8973. return new BarX(data, maybeStackX(maybeIntervalX(maybeIdentityX(options))));
  8974. }
  8975. function barY(data, options = {}) {
  8976. if (!hasXY(options)) options = {...options, x: indexOf, y2: identity$1};
  8977. return new BarY(data, maybeStackY(maybeIntervalY(maybeIdentityY(options))));
  8978. }
  8979. const defaults$b = {
  8980. ariaLabel: "cell"
  8981. };
  8982. class Cell extends AbstractBar {
  8983. constructor(data, {x, y, ...options} = {}) {
  8984. super(
  8985. data,
  8986. {
  8987. x: {value: x, scale: "x", type: "band", optional: true},
  8988. y: {value: y, scale: "y", type: "band", optional: true}
  8989. },
  8990. options,
  8991. defaults$b
  8992. );
  8993. }
  8994. _transform(selection, mark) {
  8995. // apply dx, dy
  8996. selection.call(applyTransform, mark, {}, 0, 0);
  8997. }
  8998. }
  8999. function cell(data, {x, y, ...options} = {}) {
  9000. [x, y] = maybeTuple(x, y);
  9001. return new Cell(data, {...options, x, y});
  9002. }
  9003. function cellX(data, {x = indexOf, fill, stroke, ...options} = {}) {
  9004. if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity$1;
  9005. return new Cell(data, {...options, x, fill, stroke});
  9006. }
  9007. function cellY(data, {y = indexOf, fill, stroke, ...options} = {}) {
  9008. if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity$1;
  9009. return new Cell(data, {...options, y, fill, stroke});
  9010. }
  9011. const defaults$a = {
  9012. ariaLabel: "dot",
  9013. fill: "none",
  9014. stroke: "currentColor",
  9015. strokeWidth: 1.5
  9016. };
  9017. function withDefaultSort(options) {
  9018. return options.sort === undefined && options.reverse === undefined ? sort({channel: "-r"}, options) : options;
  9019. }
  9020. class Dot extends Mark {
  9021. constructor(data, options = {}) {
  9022. const {x, y, r, rotate, symbol = d3.symbolCircle, frameAnchor} = options;
  9023. const [vrotate, crotate] = maybeNumberChannel(rotate, 0);
  9024. const [vsymbol, csymbol] = maybeSymbolChannel(symbol);
  9025. const [vr, cr] = maybeNumberChannel(r, vsymbol == null ? 3 : 4.5);
  9026. super(
  9027. data,
  9028. {
  9029. x: {value: x, scale: "x", optional: true},
  9030. y: {value: y, scale: "y", optional: true},
  9031. r: {value: vr, scale: "r", filter: positive, optional: true},
  9032. rotate: {value: vrotate, optional: true},
  9033. symbol: {value: vsymbol, scale: "auto", optional: true}
  9034. },
  9035. withDefaultSort(options),
  9036. defaults$a
  9037. );
  9038. this.r = cr;
  9039. this.rotate = crotate;
  9040. this.symbol = csymbol;
  9041. this.frameAnchor = maybeFrameAnchor(frameAnchor);
  9042. // Give a hint to the symbol scale; this allows the symbol scale to choose
  9043. // appropriate default symbols based on whether the dots are filled or
  9044. // stroked, and for the symbol legend to match the appearance of the dots.
  9045. const {channels} = this;
  9046. const {symbol: symbolChannel} = channels;
  9047. if (symbolChannel) {
  9048. const {fill: fillChannel, stroke: strokeChannel} = channels;
  9049. symbolChannel.hint = {
  9050. fill: fillChannel
  9051. ? fillChannel.value === symbolChannel.value
  9052. ? "color"
  9053. : "currentColor"
  9054. : this.fill ?? "currentColor",
  9055. stroke: strokeChannel
  9056. ? strokeChannel.value === symbolChannel.value
  9057. ? "color"
  9058. : "currentColor"
  9059. : this.stroke ?? "none"
  9060. };
  9061. }
  9062. }
  9063. render(index, scales, channels, dimensions, context) {
  9064. const {x, y} = scales;
  9065. const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels;
  9066. const {r, rotate, symbol} = this;
  9067. const [cx, cy] = applyFrameAnchor(this, dimensions);
  9068. const circle = symbol === d3.symbolCircle;
  9069. const size = R ? undefined : r * r * Math.PI;
  9070. if (negative(r)) index = [];
  9071. return create("svg:g", context)
  9072. .call(applyIndirectStyles, this, dimensions, context)
  9073. .call(applyTransform, this, {x: X && x, y: Y && y})
  9074. .call((g) =>
  9075. g
  9076. .selectAll()
  9077. .data(index)
  9078. .enter()
  9079. .append(circle ? "circle" : "path")
  9080. .call(applyDirectStyles, this)
  9081. .call(
  9082. circle
  9083. ? (selection) => {
  9084. selection
  9085. .attr("cx", X ? (i) => X[i] : cx)
  9086. .attr("cy", Y ? (i) => Y[i] : cy)
  9087. .attr("r", R ? (i) => R[i] : r);
  9088. }
  9089. : (selection) => {
  9090. selection
  9091. .attr(
  9092. "transform",
  9093. template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${
  9094. A ? (i) => ` rotate(${A[i]})` : rotate ? ` rotate(${rotate})` : ``
  9095. }`
  9096. )
  9097. .attr(
  9098. "d",
  9099. R && S
  9100. ? (i) => {
  9101. const p = d3.pathRound();
  9102. S[i].draw(p, R[i] * R[i] * Math.PI);
  9103. return p;
  9104. }
  9105. : R
  9106. ? (i) => {
  9107. const p = d3.pathRound();
  9108. symbol.draw(p, R[i] * R[i] * Math.PI);
  9109. return p;
  9110. }
  9111. : S
  9112. ? (i) => {
  9113. const p = d3.pathRound();
  9114. S[i].draw(p, size);
  9115. return p;
  9116. }
  9117. : (() => {
  9118. const p = d3.pathRound();
  9119. symbol.draw(p, size);
  9120. return p;
  9121. })()
  9122. );
  9123. }
  9124. )
  9125. .call(applyChannelStyles, this, channels)
  9126. )
  9127. .node();
  9128. }
  9129. }
  9130. function dot(data, {x, y, ...options} = {}) {
  9131. if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y);
  9132. return new Dot(data, {...options, x, y});
  9133. }
  9134. function dotX(data, {x = identity$1, ...options} = {}) {
  9135. return new Dot(data, maybeIntervalMidY({...options, x}));
  9136. }
  9137. function dotY(data, {y = identity$1, ...options} = {}) {
  9138. return new Dot(data, maybeIntervalMidX({...options, y}));
  9139. }
  9140. function circle(data, options) {
  9141. return dot(data, {...options, symbol: "circle"});
  9142. }
  9143. function hexagon(data, options) {
  9144. return dot(data, {...options, symbol: "hexagon"});
  9145. }
  9146. const defaults$9 = {
  9147. ariaLabel: "line",
  9148. fill: "none",
  9149. stroke: "currentColor",
  9150. strokeWidth: 1.5,
  9151. strokeLinecap: "round",
  9152. strokeLinejoin: "round",
  9153. strokeMiterlimit: 1
  9154. };
  9155. class Line extends Mark {
  9156. constructor(data, options = {}) {
  9157. const {x, y, z, curve, tension} = options;
  9158. super(
  9159. data,
  9160. {
  9161. x: {value: x, scale: "x"},
  9162. y: {value: y, scale: "y"},
  9163. z: {value: maybeZ(options), optional: true}
  9164. },
  9165. options,
  9166. defaults$9
  9167. );
  9168. this.z = z;
  9169. this.curve = maybeCurveAuto(curve, tension);
  9170. markers(this, options);
  9171. }
  9172. filter(index) {
  9173. return index;
  9174. }
  9175. project(channels, values, context) {
  9176. // For the auto curve, projection is handled at render.
  9177. if (this.curve !== curveAuto) {
  9178. super.project(channels, values, context);
  9179. }
  9180. }
  9181. render(index, scales, channels, dimensions, context) {
  9182. const {x: X, y: Y} = channels;
  9183. const {curve} = this;
  9184. return create("svg:g", context)
  9185. .call(applyIndirectStyles, this, dimensions, context)
  9186. .call(applyTransform, this, scales)
  9187. .call((g) =>
  9188. g
  9189. .selectAll()
  9190. .data(groupIndex(index, [X, Y], this, channels))
  9191. .enter()
  9192. .append("path")
  9193. .call(applyDirectStyles, this)
  9194. .call(applyGroupedChannelStyles, this, channels)
  9195. .call(applyGroupedMarkers, this, channels, context)
  9196. .attr(
  9197. "d",
  9198. curve === curveAuto && context.projection
  9199. ? sphereLine(context.path(), X, Y)
  9200. : d3.line()
  9201. .curve(curve)
  9202. .defined((i) => i >= 0)
  9203. .x((i) => X[i])
  9204. .y((i) => Y[i])
  9205. )
  9206. )
  9207. .node();
  9208. }
  9209. }
  9210. function sphereLine(path, X, Y) {
  9211. X = coerceNumbers(X);
  9212. Y = coerceNumbers(Y);
  9213. return (I) => {
  9214. let line = [];
  9215. const lines = [line];
  9216. for (const i of I) {
  9217. // Check for undefined value; see groupIndex.
  9218. if (i === -1) {
  9219. line = [];
  9220. lines.push(line);
  9221. } else {
  9222. line.push([X[i], Y[i]]);
  9223. }
  9224. }
  9225. return path({type: "MultiLineString", coordinates: lines});
  9226. };
  9227. }
  9228. function line(data, {x, y, ...options} = {}) {
  9229. [x, y] = maybeTuple(x, y);
  9230. return new Line(data, {...options, x, y});
  9231. }
  9232. function lineX(data, {x = identity$1, y = indexOf, ...options} = {}) {
  9233. return new Line(data, maybeDenseIntervalY({...options, x, y}));
  9234. }
  9235. function lineY(data, {x = indexOf, y = identity$1, ...options} = {}) {
  9236. return new Line(data, maybeDenseIntervalX({...options, x, y}));
  9237. }
  9238. function autoSpec(data, options) {
  9239. options = normalizeOptions(options);
  9240. // Greedily materialize columns for type inference; we’ll need them anyway to
  9241. // plot! Note that we don’t apply any type inference to the fx and fy
  9242. // channels, if present; these are always ordinal (at least for now).
  9243. const {x, y, color, size} = options;
  9244. const X = materializeValue(data, x);
  9245. const Y = materializeValue(data, y);
  9246. const C = materializeValue(data, color);
  9247. const S = materializeValue(data, size);
  9248. // Compute the default options.
  9249. let {
  9250. fx,
  9251. fy,
  9252. x: {value: xValue, reduce: xReduce, zero: xZero, ...xOptions},
  9253. y: {value: yValue, reduce: yReduce, zero: yZero, ...yOptions},
  9254. color: {value: colorValue, color: colorColor, reduce: colorReduce},
  9255. size: {value: sizeValue, reduce: sizeReduce}, // TODO constant radius?
  9256. mark
  9257. } = options;
  9258. // Determine the default reducer, if any.
  9259. if (xReduce === undefined)
  9260. xReduce = yReduce == null && xValue == null && sizeValue == null && yValue != null ? "count" : null;
  9261. if (yReduce === undefined)
  9262. yReduce = xReduce == null && yValue == null && sizeValue == null && xValue != null ? "count" : null;
  9263. // Determine the default size reducer, if any.
  9264. if (
  9265. sizeReduce === undefined &&
  9266. sizeValue == null &&
  9267. colorReduce == null &&
  9268. xReduce == null &&
  9269. yReduce == null &&
  9270. (xValue == null || isOrdinal(X)) &&
  9271. (yValue == null || isOrdinal(Y))
  9272. ) {
  9273. sizeReduce = "count";
  9274. }
  9275. // Determine the default zero-ness.
  9276. if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined;
  9277. if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined;
  9278. // TODO Shorthand: array of primitives should result in a histogram
  9279. if (xValue == null && yValue == null) throw new Error("must specify x or y");
  9280. if (xReduce != null && yValue == null) throw new Error("reducing x requires y");
  9281. if (yReduce != null && xValue == null) throw new Error("reducing y requires x");
  9282. // Determine the default mark type.
  9283. if (mark === undefined) {
  9284. mark =
  9285. sizeValue != null || sizeReduce != null
  9286. ? "dot"
  9287. : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap
  9288. ? "bar"
  9289. : xValue != null && yValue != null
  9290. ? isOrdinal(X) || isOrdinal(Y) || (xReduce == null && yReduce == null && !isMonotonic(X) && !isMonotonic(Y))
  9291. ? "dot"
  9292. : "line"
  9293. : xValue != null || yValue != null
  9294. ? "rule"
  9295. : null;
  9296. }
  9297. let Z; // may be set to null to disable series-by-color for line and area
  9298. let colorMode; // "fill" or "stroke"
  9299. // Determine the mark implementation.
  9300. let markImpl;
  9301. switch (mark) {
  9302. case "dot":
  9303. markImpl = dot;
  9304. colorMode = "stroke";
  9305. break;
  9306. case "line":
  9307. markImpl =
  9308. (X && Y) || xReduce != null || yReduce != null // same logic as area (see below), but default to line
  9309. ? yZero || yReduce != null || (X && isMonotonic(X))
  9310. ? lineY
  9311. : xZero || xReduce != null || (Y && isMonotonic(Y))
  9312. ? lineX
  9313. : line
  9314. : X // 1d line by index
  9315. ? lineX
  9316. : lineY;
  9317. colorMode = "stroke";
  9318. if (isHighCardinality(C)) Z = null; // TODO only if z not set by user
  9319. break;
  9320. case "area":
  9321. markImpl = !(yZero || yReduce != null) && (xZero || xReduce != null || (Y && isMonotonic(Y))) ? areaX : areaY; // favor areaY if unsure
  9322. colorMode = "fill";
  9323. if (isHighCardinality(C)) Z = null; // TODO only if z not set by user
  9324. break;
  9325. case "rule":
  9326. markImpl = X ? ruleX : ruleY;
  9327. colorMode = "stroke";
  9328. break;
  9329. case "bar":
  9330. markImpl =
  9331. xReduce != null // bin or group on y
  9332. ? isOrdinal(Y)
  9333. ? isSelectReducer(xReduce) && X && isOrdinal(X)
  9334. ? cell
  9335. : barX
  9336. : rectX
  9337. : yReduce != null // bin or group on x
  9338. ? isOrdinal(X)
  9339. ? isSelectReducer(yReduce) && Y && isOrdinal(Y)
  9340. ? cell
  9341. : barY
  9342. : rectY
  9343. : colorReduce != null || sizeReduce != null // bin or group on both x and y
  9344. ? X && isOrdinal(X) && Y && isOrdinal(Y)
  9345. ? cell
  9346. : X && isOrdinal(X)
  9347. ? barY
  9348. : Y && isOrdinal(Y)
  9349. ? barX
  9350. : rect
  9351. : X && isNumeric(X) && !(Y && isNumeric(Y))
  9352. ? barX // if y is temporal, treat as ordinal
  9353. : Y && isNumeric(Y) && !(X && isNumeric(X))
  9354. ? barY // if x is temporal, treat as ordinal
  9355. : cell;
  9356. colorMode = "fill";
  9357. break;
  9358. default:
  9359. throw new Error(`invalid mark: ${mark}`);
  9360. }
  9361. // Determine the mark options.
  9362. let markOptions = {
  9363. fx,
  9364. fy,
  9365. x: X ?? undefined, // treat null x as undefined for implicit stack
  9366. y: Y ?? undefined, // treat null y as undefined for implicit stack
  9367. [colorMode]: C ?? colorColor,
  9368. z: Z,
  9369. r: S ?? undefined, // treat null size as undefined for default constant radius
  9370. tip: true
  9371. };
  9372. let transformImpl;
  9373. let transformOptions = {[colorMode]: colorReduce ?? undefined, r: sizeReduce ?? undefined};
  9374. if (xReduce != null && yReduce != null) {
  9375. throw new Error(`cannot reduce both x and y`); // for now at least
  9376. } else if (yReduce != null) {
  9377. transformOptions.y = yReduce;
  9378. transformImpl = isOrdinal(X) ? groupX : binX;
  9379. } else if (xReduce != null) {
  9380. transformOptions.x = xReduce;
  9381. transformImpl = isOrdinal(Y) ? groupY : binY;
  9382. } else if (colorReduce != null || sizeReduce != null) {
  9383. if (X && Y) {
  9384. transformImpl = isOrdinal(X) && isOrdinal(Y) ? group : isOrdinal(X) ? binY : isOrdinal(Y) ? binX : bin;
  9385. } else if (X) {
  9386. transformImpl = isOrdinal(X) ? groupX : binX;
  9387. } else if (Y) {
  9388. transformImpl = isOrdinal(Y) ? groupY : binY;
  9389. }
  9390. }
  9391. // When using the bin transform, pass through additional options (e.g., thresholds).
  9392. if (transformImpl === bin || transformImpl === binX) markOptions.x = {value: X, ...xOptions};
  9393. if (transformImpl === bin || transformImpl === binY) markOptions.y = {value: Y, ...yOptions};
  9394. // If zero-ness is not specified, default based on whether the resolved mark
  9395. // type will include a zero baseline.
  9396. if (xZero === undefined)
  9397. xZero =
  9398. X &&
  9399. !(transformImpl === bin || transformImpl === binX) &&
  9400. (markImpl === barX || markImpl === areaX || markImpl === rectX || markImpl === ruleY);
  9401. if (yZero === undefined)
  9402. yZero =
  9403. Y &&
  9404. !(transformImpl === bin || transformImpl === binY) &&
  9405. (markImpl === barY || markImpl === areaY || markImpl === rectY || markImpl === ruleX);
  9406. return {
  9407. fx: fx ?? null,
  9408. fy: fy ?? null,
  9409. x: {
  9410. value: xValue ?? null,
  9411. reduce: xReduce ?? null,
  9412. zero: !!xZero,
  9413. ...xOptions
  9414. },
  9415. y: {
  9416. value: yValue ?? null,
  9417. reduce: yReduce ?? null,
  9418. zero: !!yZero,
  9419. ...yOptions
  9420. },
  9421. color: {
  9422. value: colorValue ?? null,
  9423. reduce: colorReduce ?? null,
  9424. ...(colorColor !== undefined && {color: colorColor})
  9425. },
  9426. size: {
  9427. value: sizeValue ?? null,
  9428. reduce: sizeReduce ?? null
  9429. },
  9430. mark,
  9431. markImpl: implNames[markImpl],
  9432. markOptions,
  9433. transformImpl: implNames[transformImpl],
  9434. transformOptions,
  9435. colorMode
  9436. };
  9437. }
  9438. function auto(data, options) {
  9439. const spec = autoSpec(data, options);
  9440. const {
  9441. fx,
  9442. fy,
  9443. x: {zero: xZero},
  9444. y: {zero: yZero},
  9445. markOptions,
  9446. transformOptions,
  9447. colorMode
  9448. } = spec;
  9449. const markImpl = impls[spec.markImpl];
  9450. const transformImpl = impls[spec.transformImpl];
  9451. // In the case of filled marks (particularly bars and areas) the frame and
  9452. // rules should come after the mark; in the case of stroked marks
  9453. // (particularly dots and lines) they should come before the mark.
  9454. const frames = fx != null || fy != null ? frame({strokeOpacity: 0.1}) : null;
  9455. const rules = [xZero ? ruleX([0]) : null, yZero ? ruleY([0]) : null];
  9456. const mark = markImpl(data, transformImpl ? transformImpl(transformOptions, markOptions) : markOptions);
  9457. return colorMode === "stroke" ? marks(frames, rules, mark) : marks(frames, mark, rules);
  9458. }
  9459. // TODO What about sorted within series?
  9460. function isMonotonic(values) {
  9461. let previous;
  9462. let previousOrder;
  9463. for (const value of values) {
  9464. if (value == null) continue;
  9465. if (previous === undefined) {
  9466. previous = value;
  9467. continue;
  9468. }
  9469. const order = Math.sign(d3.ascending(previous, value));
  9470. if (!order) continue; // skip zero, NaN
  9471. if (previousOrder !== undefined && order !== previousOrder) return false;
  9472. previous = value;
  9473. previousOrder = order;
  9474. }
  9475. return true;
  9476. }
  9477. // Allow x and y and other dimensions to be specified as shorthand field names
  9478. // (but note that they can also be specified as a {transform} object such as
  9479. // Plot.identity). We don’t support reducers for the faceting, but for symmetry
  9480. // with x and y we allow facets to be specified as {value} objects.
  9481. function normalizeOptions({x, y, color, size, fx, fy, mark} = {}) {
  9482. if (!isOptions(x)) x = makeOptions(x);
  9483. if (!isOptions(y)) y = makeOptions(y);
  9484. if (!isOptions(color)) color = isColor(color) ? {color} : makeOptions(color);
  9485. if (!isOptions(size)) size = makeOptions(size);
  9486. if (isOptions(fx)) ({value: fx} = makeOptions(fx));
  9487. if (isOptions(fy)) ({value: fy} = makeOptions(fy));
  9488. if (mark != null) mark = `${mark}`.toLowerCase();
  9489. return {x, y, color, size, fx, fy, mark};
  9490. }
  9491. // To apply heuristics based on the data types (values), realize the columns. We
  9492. // could maybe look at the data.schema here, but Plot’s behavior depends on the
  9493. // actual values anyway, so this probably is what we want.
  9494. function materializeValue(data, options) {
  9495. const V = valueof(data, options.value);
  9496. if (V) V.label = labelof(options.value);
  9497. return V;
  9498. }
  9499. function makeOptions(value) {
  9500. return isReducer(value) ? {reduce: value} : {value};
  9501. }
  9502. // The distinct, count, sum, and proportion reducers are additive (stackable).
  9503. function isZeroReducer(reduce) {
  9504. return /^(?:distinct|count|sum|proportion)$/i.test(reduce);
  9505. }
  9506. // The first, last, and mode reducers preserve the type of the aggregated values.
  9507. function isSelectReducer(reduce) {
  9508. return /^(?:first|last|mode)$/i.test(reduce);
  9509. }
  9510. // https://github.com/observablehq/plot/blob/818562649280e155136f730fc496e0b3d15ae464/src/transforms/group.js#L236
  9511. function isReducer(reduce) {
  9512. if (reduce == null) return false;
  9513. if (typeof reduce.reduceIndex === "function") return true;
  9514. if (typeof reduce.reduce === "function" && isObject(reduce)) return true; // N.B. array.reduce
  9515. if (/^p\d{2}$/i.test(reduce)) return true;
  9516. switch (`${reduce}`.toLowerCase()) {
  9517. case "first":
  9518. case "last":
  9519. case "count":
  9520. case "distinct":
  9521. case "sum":
  9522. case "proportion":
  9523. case "proportion-facet": // TODO remove me?
  9524. case "deviation":
  9525. case "min":
  9526. case "min-index": // TODO remove me?
  9527. case "max":
  9528. case "max-index": // TODO remove me?
  9529. case "mean":
  9530. case "median":
  9531. case "variance":
  9532. case "mode":
  9533. // These are technically reducers, but I think we’d want to treat them as fields?
  9534. // case "x":
  9535. // case "x1":
  9536. // case "x2":
  9537. // case "y":
  9538. // case "y1":
  9539. // case "y2":
  9540. return true;
  9541. }
  9542. return false;
  9543. }
  9544. function isHighCardinality(value) {
  9545. return value ? new d3.InternSet(value).size > value.length >> 1 : false;
  9546. }
  9547. const impls = {
  9548. dot,
  9549. line,
  9550. lineX,
  9551. lineY,
  9552. areaX,
  9553. areaY,
  9554. ruleX,
  9555. ruleY,
  9556. barX,
  9557. barY,
  9558. rect,
  9559. rectX,
  9560. rectY,
  9561. cell,
  9562. bin,
  9563. binX,
  9564. binY,
  9565. group,
  9566. groupX,
  9567. groupY
  9568. };
  9569. // Instead of returning the mark or transform implementation directly, we return
  9570. // the implementation name to facilitate code compilation (“eject to explicit
  9571. // marks”). An implementation-to-name mapping needs to live somewhere for
  9572. // compilation, and by having it in Plot we can more easily introduce a new mark
  9573. // or transform implementation in Plot.auto without having to synchronize a
  9574. // downstream change in the compiler.
  9575. const implNames = Object.fromEntries(Object.entries(impls).map(([name, impl]) => [impl, name]));
  9576. function mapX(mapper, options = {}) {
  9577. let {x, x1, x2} = options;
  9578. if (x === undefined && x1 === undefined && x2 === undefined) options = {...options, x: (x = identity$1)};
  9579. const outputs = {};
  9580. if (x != null) outputs.x = mapper;
  9581. if (x1 != null) outputs.x1 = mapper;
  9582. if (x2 != null) outputs.x2 = mapper;
  9583. return map(outputs, options);
  9584. }
  9585. function mapY(mapper, options = {}) {
  9586. let {y, y1, y2} = options;
  9587. if (y === undefined && y1 === undefined && y2 === undefined) options = {...options, y: (y = identity$1)};
  9588. const outputs = {};
  9589. if (y != null) outputs.y = mapper;
  9590. if (y1 != null) outputs.y1 = mapper;
  9591. if (y2 != null) outputs.y2 = mapper;
  9592. return map(outputs, options);
  9593. }
  9594. function map(outputs = {}, options = {}) {
  9595. const z = maybeZ(options);
  9596. const channels = Object.entries(outputs).map(([key, map]) => {
  9597. const input = maybeInput(key, options);
  9598. if (input == null) throw new Error(`missing channel: ${key}`);
  9599. const [output, setOutput] = column(input);
  9600. return {key, input, output, setOutput, map: maybeMap(map)};
  9601. });
  9602. return {
  9603. ...basic(options, (data, facets) => {
  9604. const Z = valueof(data, z);
  9605. const X = channels.map(({input}) => valueof(data, input));
  9606. const MX = channels.map(({setOutput}) => setOutput(new Array(data.length)));
  9607. for (const facet of facets) {
  9608. for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) {
  9609. channels.forEach(({map}, i) => map.mapIndex(I, X[i], MX[i]));
  9610. }
  9611. }
  9612. return {data, facets};
  9613. }),
  9614. ...Object.fromEntries(channels.map(({key, output}) => [key, output]))
  9615. };
  9616. }
  9617. function maybeMap(map) {
  9618. if (map == null) throw new Error("missing map");
  9619. if (typeof map.mapIndex === "function") return map;
  9620. if (typeof map.map === "function" && isObject(map)) return mapMap(map); // N.B. array.map
  9621. if (typeof map === "function") return mapFunction(taker(map));
  9622. switch (`${map}`.toLowerCase()) {
  9623. case "cumsum":
  9624. return mapCumsum;
  9625. case "rank":
  9626. return mapFunction((I, V) => d3.rank(I, (i) => V[i]));
  9627. case "quantile":
  9628. return mapFunction((I, V) => rankQuantile(I, (i) => V[i]));
  9629. }
  9630. throw new Error(`invalid map: ${map}`);
  9631. }
  9632. function mapMap(map) {
  9633. console.warn("deprecated map interface; implement mapIndex instead.");
  9634. return {mapIndex: map.map.bind(map)};
  9635. }
  9636. function rankQuantile(I, f) {
  9637. const n = d3.count(I, f) - 1;
  9638. return d3.rank(I, f).map((r) => r / n);
  9639. }
  9640. function mapFunction(f) {
  9641. return {
  9642. mapIndex(I, S, T) {
  9643. const M = f(I, S);
  9644. if (M.length !== I.length) throw new Error("map function returned a mismatched length");
  9645. for (let i = 0, n = I.length; i < n; ++i) T[I[i]] = M[i];
  9646. }
  9647. };
  9648. }
  9649. const mapCumsum = {
  9650. mapIndex(I, S, T) {
  9651. let sum = 0;
  9652. for (const i of I) T[i] = sum += S[i];
  9653. }
  9654. };
  9655. function windowX(windowOptions = {}, options) {
  9656. if (arguments.length === 1) options = windowOptions;
  9657. return mapX(window$1(windowOptions), options);
  9658. }
  9659. function windowY(windowOptions = {}, options) {
  9660. if (arguments.length === 1) options = windowOptions;
  9661. return mapY(window$1(windowOptions), options);
  9662. }
  9663. function window$1(options = {}) {
  9664. if (typeof options === "number") options = {k: options};
  9665. let {k, reduce, shift, anchor, strict} = options;
  9666. if (anchor === undefined && shift !== undefined) {
  9667. anchor = maybeShift(shift);
  9668. warn(`Warning: the shift option is deprecated; please use anchor "${anchor}" instead.`);
  9669. }
  9670. if (!((k = Math.floor(k)) > 0)) throw new Error(`invalid k: ${k}`);
  9671. return maybeReduce(reduce)(k, maybeAnchor$1(anchor, k), strict);
  9672. }
  9673. function maybeAnchor$1(anchor = "middle", k) {
  9674. switch (`${anchor}`.toLowerCase()) {
  9675. case "middle":
  9676. return (k - 1) >> 1;
  9677. case "start":
  9678. return 0;
  9679. case "end":
  9680. return k - 1;
  9681. }
  9682. throw new Error(`invalid anchor: ${anchor}`);
  9683. }
  9684. function maybeShift(shift) {
  9685. switch (`${shift}`.toLowerCase()) {
  9686. case "centered":
  9687. return "middle";
  9688. case "leading":
  9689. return "start";
  9690. case "trailing":
  9691. return "end";
  9692. }
  9693. throw new Error(`invalid shift: ${shift}`);
  9694. }
  9695. function maybeReduce(reduce = "mean") {
  9696. if (typeof reduce === "string") {
  9697. if (/^p\d{2}$/i.test(reduce)) return reduceAccessor(percentile(reduce));
  9698. switch (reduce.toLowerCase()) {
  9699. case "deviation":
  9700. return reduceAccessor(d3.deviation);
  9701. case "max":
  9702. return reduceArray((I, V) => d3.max(I, (i) => V[i]));
  9703. case "mean":
  9704. return reduceMean;
  9705. case "median":
  9706. return reduceAccessor(d3.median);
  9707. case "min":
  9708. return reduceArray((I, V) => d3.min(I, (i) => V[i]));
  9709. case "mode":
  9710. return reduceArray((I, V) => d3.mode(I, (i) => V[i]));
  9711. case "sum":
  9712. return reduceSum;
  9713. case "variance":
  9714. return reduceAccessor(d3.variance);
  9715. case "difference":
  9716. return reduceDifference;
  9717. case "ratio":
  9718. return reduceRatio;
  9719. case "first":
  9720. return reduceFirst;
  9721. case "last":
  9722. return reduceLast;
  9723. }
  9724. }
  9725. if (typeof reduce !== "function") throw new Error(`invalid reduce: ${reduce}`);
  9726. return reduceArray(taker(reduce));
  9727. }
  9728. // Note that the subarray may include NaN in the non-strict case; we expect the
  9729. // function f to handle that itself (e.g., by filtering as needed). The D3
  9730. // reducers (e.g., min, max, mean, median) do, and it’s faster to avoid
  9731. // redundant filtering.
  9732. function reduceAccessor(f) {
  9733. return (k, s, strict) =>
  9734. strict
  9735. ? {
  9736. mapIndex(I, S, T) {
  9737. const v = (i) => (S[i] == null ? NaN : +S[i]);
  9738. let nans = 0;
  9739. for (let i = 0; i < k - 1; ++i) if (isNaN(v(i))) ++nans;
  9740. for (let i = 0, n = I.length - k + 1; i < n; ++i) {
  9741. if (isNaN(v(i + k - 1))) ++nans;
  9742. T[I[i + s]] = nans === 0 ? f(subarray(I, i, i + k), v) : NaN;
  9743. if (isNaN(v(i))) --nans;
  9744. }
  9745. }
  9746. }
  9747. : {
  9748. mapIndex(I, S, T) {
  9749. const v = (i) => (S[i] == null ? NaN : +S[i]);
  9750. for (let i = -s; i < 0; ++i) {
  9751. T[I[i + s]] = f(subarray(I, 0, i + k), v);
  9752. }
  9753. for (let i = 0, n = I.length - s; i < n; ++i) {
  9754. T[I[i + s]] = f(subarray(I, i, i + k), v);
  9755. }
  9756. }
  9757. };
  9758. }
  9759. function reduceArray(f) {
  9760. return (k, s, strict) =>
  9761. strict
  9762. ? {
  9763. mapIndex(I, S, T) {
  9764. let count = 0;
  9765. for (let i = 0; i < k - 1; ++i) count += defined(S[I[i]]);
  9766. for (let i = 0, n = I.length - k + 1; i < n; ++i) {
  9767. count += defined(S[I[i + k - 1]]);
  9768. if (count === k) T[I[i + s]] = f(subarray(I, i, i + k), S);
  9769. count -= defined(S[I[i]]);
  9770. }
  9771. }
  9772. }
  9773. : {
  9774. mapIndex(I, S, T) {
  9775. for (let i = -s; i < 0; ++i) {
  9776. T[I[i + s]] = f(subarray(I, 0, i + k), S);
  9777. }
  9778. for (let i = 0, n = I.length - s; i < n; ++i) {
  9779. T[I[i + s]] = f(subarray(I, i, i + k), S);
  9780. }
  9781. }
  9782. };
  9783. }
  9784. function reduceSum(k, s, strict) {
  9785. return strict
  9786. ? {
  9787. mapIndex(I, S, T) {
  9788. let nans = 0;
  9789. let sum = 0;
  9790. for (let i = 0; i < k - 1; ++i) {
  9791. const v = S[I[i]];
  9792. if (v === null || isNaN(v)) ++nans;
  9793. else sum += +v;
  9794. }
  9795. for (let i = 0, n = I.length - k + 1; i < n; ++i) {
  9796. const a = S[I[i]];
  9797. const b = S[I[i + k - 1]];
  9798. if (b === null || isNaN(b)) ++nans;
  9799. else sum += +b;
  9800. T[I[i + s]] = nans === 0 ? sum : NaN;
  9801. if (a === null || isNaN(a)) --nans;
  9802. else sum -= +a;
  9803. }
  9804. }
  9805. }
  9806. : {
  9807. mapIndex(I, S, T) {
  9808. let sum = 0;
  9809. const n = I.length;
  9810. for (let i = 0, j = Math.min(n, k - s - 1); i < j; ++i) {
  9811. sum += +S[I[i]] || 0;
  9812. }
  9813. for (let i = -s, j = n - s; i < j; ++i) {
  9814. sum += +S[I[i + k - 1]] || 0;
  9815. T[I[i + s]] = sum;
  9816. sum -= +S[I[i]] || 0;
  9817. }
  9818. }
  9819. };
  9820. }
  9821. function reduceMean(k, s, strict) {
  9822. if (strict) {
  9823. const sum = reduceSum(k, s, strict);
  9824. return {
  9825. mapIndex(I, S, T) {
  9826. sum.mapIndex(I, S, T);
  9827. for (let i = 0, n = I.length - k + 1; i < n; ++i) {
  9828. T[I[i + s]] /= k;
  9829. }
  9830. }
  9831. };
  9832. } else {
  9833. return {
  9834. mapIndex(I, S, T) {
  9835. let sum = 0;
  9836. let count = 0;
  9837. const n = I.length;
  9838. for (let i = 0, j = Math.min(n, k - s - 1); i < j; ++i) {
  9839. let v = S[I[i]];
  9840. if (v !== null && !isNaN((v = +v))) (sum += v), ++count;
  9841. }
  9842. for (let i = -s, j = n - s; i < j; ++i) {
  9843. let a = S[I[i + k - 1]];
  9844. let b = S[I[i]];
  9845. if (a !== null && !isNaN((a = +a))) (sum += a), ++count;
  9846. T[I[i + s]] = sum / count;
  9847. if (b !== null && !isNaN((b = +b))) (sum -= b), --count;
  9848. }
  9849. }
  9850. };
  9851. }
  9852. }
  9853. function firstDefined(S, I, i, k) {
  9854. for (let j = i + k; i < j; ++i) {
  9855. const v = S[I[i]];
  9856. if (defined(v)) return v;
  9857. }
  9858. }
  9859. function lastDefined(S, I, i, k) {
  9860. for (let j = i + k - 1; j >= i; --j) {
  9861. const v = S[I[j]];
  9862. if (defined(v)) return v;
  9863. }
  9864. }
  9865. function firstNumber(S, I, i, k) {
  9866. for (let j = i + k; i < j; ++i) {
  9867. let v = S[I[i]];
  9868. if (v !== null && !isNaN((v = +v))) return v;
  9869. }
  9870. }
  9871. function lastNumber(S, I, i, k) {
  9872. for (let j = i + k - 1; j >= i; --j) {
  9873. let v = S[I[j]];
  9874. if (v !== null && !isNaN((v = +v))) return v;
  9875. }
  9876. }
  9877. function reduceDifference(k, s, strict) {
  9878. return strict
  9879. ? {
  9880. mapIndex(I, S, T) {
  9881. for (let i = 0, n = I.length - k; i < n; ++i) {
  9882. const a = S[I[i]];
  9883. const b = S[I[i + k - 1]];
  9884. T[I[i + s]] = a === null || b === null ? NaN : b - a;
  9885. }
  9886. }
  9887. }
  9888. : {
  9889. mapIndex(I, S, T) {
  9890. for (let i = -s, n = I.length - k + s + 1; i < n; ++i) {
  9891. T[I[i + s]] = lastNumber(S, I, i, k) - firstNumber(S, I, i, k);
  9892. }
  9893. }
  9894. };
  9895. }
  9896. function reduceRatio(k, s, strict) {
  9897. return strict
  9898. ? {
  9899. mapIndex(I, S, T) {
  9900. for (let i = 0, n = I.length - k; i < n; ++i) {
  9901. const a = S[I[i]];
  9902. const b = S[I[i + k - 1]];
  9903. T[I[i + s]] = a === null || b === null ? NaN : b / a;
  9904. }
  9905. }
  9906. }
  9907. : {
  9908. mapIndex(I, S, T) {
  9909. for (let i = -s, n = I.length - k + s + 1; i < n; ++i) {
  9910. T[I[i + s]] = lastNumber(S, I, i, k) / firstNumber(S, I, i, k);
  9911. }
  9912. }
  9913. };
  9914. }
  9915. function reduceFirst(k, s, strict) {
  9916. return strict
  9917. ? {
  9918. mapIndex(I, S, T) {
  9919. for (let i = 0, n = I.length - k; i < n; ++i) {
  9920. T[I[i + s]] = S[I[i]];
  9921. }
  9922. }
  9923. }
  9924. : {
  9925. mapIndex(I, S, T) {
  9926. for (let i = -s, n = I.length - k + s + 1; i < n; ++i) {
  9927. T[I[i + s]] = firstDefined(S, I, i, k);
  9928. }
  9929. }
  9930. };
  9931. }
  9932. function reduceLast(k, s, strict) {
  9933. return strict
  9934. ? {
  9935. mapIndex(I, S, T) {
  9936. for (let i = 0, n = I.length - k; i < n; ++i) {
  9937. T[I[i + s]] = S[I[i + k - 1]];
  9938. }
  9939. }
  9940. }
  9941. : {
  9942. mapIndex(I, S, T) {
  9943. for (let i = -s, n = I.length - k + s + 1; i < n; ++i) {
  9944. T[I[i + s]] = lastDefined(S, I, i, k);
  9945. }
  9946. }
  9947. };
  9948. }
  9949. const defaults$8 = {
  9950. n: 20,
  9951. k: 2,
  9952. color: "currentColor",
  9953. opacity: 0.2,
  9954. strict: true,
  9955. anchor: "end"
  9956. };
  9957. function bollingerX(
  9958. data,
  9959. {
  9960. x = identity$1,
  9961. y,
  9962. k = defaults$8.k,
  9963. color = defaults$8.color,
  9964. opacity = defaults$8.opacity,
  9965. fill = color,
  9966. fillOpacity = opacity,
  9967. stroke = color,
  9968. strokeOpacity,
  9969. strokeWidth,
  9970. ...options
  9971. } = {}
  9972. ) {
  9973. return marks(
  9974. isNoneish(fill)
  9975. ? null
  9976. : areaX(
  9977. data,
  9978. map(
  9979. {x1: bollinger({k: -k, ...options}), x2: bollinger({k, ...options})},
  9980. {x1: x, x2: x, y, fill, fillOpacity, ...options}
  9981. )
  9982. ),
  9983. isNoneish(stroke)
  9984. ? null
  9985. : lineX(data, map({x: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
  9986. );
  9987. }
  9988. function bollingerY(
  9989. data,
  9990. {
  9991. x,
  9992. y = identity$1,
  9993. k = defaults$8.k,
  9994. color = defaults$8.color,
  9995. opacity = defaults$8.opacity,
  9996. fill = color,
  9997. fillOpacity = opacity,
  9998. stroke = color,
  9999. strokeOpacity,
  10000. strokeWidth,
  10001. ...options
  10002. } = {}
  10003. ) {
  10004. return marks(
  10005. isNoneish(fill)
  10006. ? null
  10007. : areaY(
  10008. data,
  10009. map(
  10010. {y1: bollinger({k: -k, ...options}), y2: bollinger({k, ...options})},
  10011. {x, y1: y, y2: y, fill, fillOpacity, ...options}
  10012. )
  10013. ),
  10014. isNoneish(stroke)
  10015. ? null
  10016. : lineY(data, map({y: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options}))
  10017. );
  10018. }
  10019. function bollinger({n = defaults$8.n, k = 0, strict = defaults$8.strict, anchor = defaults$8.anchor} = {}) {
  10020. return window$1({k: n, reduce: (Y) => d3.mean(Y) + k * (d3.deviation(Y) || 0), strict, anchor});
  10021. }
  10022. const defaults$7 = {
  10023. ariaLabel: "tick",
  10024. fill: null,
  10025. stroke: "currentColor"
  10026. };
  10027. class AbstractTick extends Mark {
  10028. constructor(data, channels, options) {
  10029. super(data, channels, options, defaults$7);
  10030. markers(this, options);
  10031. }
  10032. render(index, scales, channels, dimensions, context) {
  10033. return create("svg:g", context)
  10034. .call(applyIndirectStyles, this, dimensions, context)
  10035. .call(this._transform, this, scales)
  10036. .call((g) =>
  10037. g
  10038. .selectAll()
  10039. .data(index)
  10040. .enter()
  10041. .append("line")
  10042. .call(applyDirectStyles, this)
  10043. .attr("x1", this._x1(scales, channels, dimensions))
  10044. .attr("x2", this._x2(scales, channels, dimensions))
  10045. .attr("y1", this._y1(scales, channels, dimensions))
  10046. .attr("y2", this._y2(scales, channels, dimensions))
  10047. .call(applyChannelStyles, this, channels)
  10048. .call(applyMarkers, this, channels, context)
  10049. )
  10050. .node();
  10051. }
  10052. }
  10053. class TickX extends AbstractTick {
  10054. constructor(data, options = {}) {
  10055. const {x, y, inset = 0, insetTop = inset, insetBottom = inset} = options;
  10056. super(
  10057. data,
  10058. {
  10059. x: {value: x, scale: "x"},
  10060. y: {value: y, scale: "y", type: "band", optional: true}
  10061. },
  10062. options
  10063. );
  10064. this.insetTop = number$1(insetTop);
  10065. this.insetBottom = number$1(insetBottom);
  10066. }
  10067. _transform(selection, mark, {x}) {
  10068. selection.call(applyTransform, mark, {x}, offset, 0);
  10069. }
  10070. _x1(scales, {x: X}) {
  10071. return (i) => X[i];
  10072. }
  10073. _x2(scales, {x: X}) {
  10074. return (i) => X[i];
  10075. }
  10076. _y1({y}, {y: Y}, {marginTop}) {
  10077. const {insetTop} = this;
  10078. return Y && y ? (i) => Y[i] + insetTop : marginTop + insetTop;
  10079. }
  10080. _y2({y}, {y: Y}, {height, marginBottom}) {
  10081. const {insetBottom} = this;
  10082. return Y && y ? (i) => Y[i] + y.bandwidth() - insetBottom : height - marginBottom - insetBottom;
  10083. }
  10084. }
  10085. class TickY extends AbstractTick {
  10086. constructor(data, options = {}) {
  10087. const {x, y, inset = 0, insetRight = inset, insetLeft = inset} = options;
  10088. super(
  10089. data,
  10090. {
  10091. y: {value: y, scale: "y"},
  10092. x: {value: x, scale: "x", type: "band", optional: true}
  10093. },
  10094. options
  10095. );
  10096. this.insetRight = number$1(insetRight);
  10097. this.insetLeft = number$1(insetLeft);
  10098. }
  10099. _transform(selection, mark, {y}) {
  10100. selection.call(applyTransform, mark, {y}, 0, offset);
  10101. }
  10102. _x1({x}, {x: X}, {marginLeft}) {
  10103. const {insetLeft} = this;
  10104. return X && x ? (i) => X[i] + insetLeft : marginLeft + insetLeft;
  10105. }
  10106. _x2({x}, {x: X}, {width, marginRight}) {
  10107. const {insetRight} = this;
  10108. return X && x ? (i) => X[i] + x.bandwidth() - insetRight : width - marginRight - insetRight;
  10109. }
  10110. _y1(scales, {y: Y}) {
  10111. return (i) => Y[i];
  10112. }
  10113. _y2(scales, {y: Y}) {
  10114. return (i) => Y[i];
  10115. }
  10116. }
  10117. function tickX(data, {x = identity$1, ...options} = {}) {
  10118. return new TickX(data, {...options, x});
  10119. }
  10120. function tickY(data, {y = identity$1, ...options} = {}) {
  10121. return new TickY(data, {...options, y});
  10122. }
  10123. // Returns a composite mark for producing a horizontal box plot, applying the
  10124. // necessary statistical transforms. The boxes are grouped by y, if present.
  10125. function boxX(
  10126. data,
  10127. {
  10128. x = identity$1,
  10129. y = null,
  10130. r,
  10131. fill = "#ccc",
  10132. fillOpacity,
  10133. stroke = "currentColor",
  10134. strokeOpacity,
  10135. strokeWidth = 2,
  10136. sort,
  10137. ...options
  10138. } = {}
  10139. ) {
  10140. const group = y != null ? groupY : groupZ$1;
  10141. return marks(
  10142. ruleY(data, group({x1: loqr1, x2: hiqr2}, {x, y, stroke, strokeOpacity, ...options})),
  10143. barX(data, group({x1: "p25", x2: "p75"}, {x, y, fill, fillOpacity, ...options})),
  10144. tickX(data, group({x: "p50"}, {x, y, stroke, strokeOpacity, strokeWidth, sort, ...options})),
  10145. dot(data, map({x: oqr}, {x, y, z: y, r, stroke, strokeOpacity, ...options}))
  10146. );
  10147. }
  10148. // Returns a composite mark for producing a vertical box plot, applying the
  10149. // necessary statistical transforms. The boxes are grouped by x, if present.
  10150. function boxY(
  10151. data,
  10152. {
  10153. y = identity$1,
  10154. x = null,
  10155. r,
  10156. fill = "#ccc",
  10157. fillOpacity,
  10158. stroke = "currentColor",
  10159. strokeOpacity,
  10160. strokeWidth = 2,
  10161. sort,
  10162. ...options
  10163. } = {}
  10164. ) {
  10165. const group = x != null ? groupX : groupZ$1;
  10166. return marks(
  10167. ruleX(data, group({y1: loqr1, y2: hiqr2}, {x, y, stroke, strokeOpacity, ...options})),
  10168. barY(data, group({y1: "p25", y2: "p75"}, {x, y, fill, fillOpacity, ...options})),
  10169. tickY(data, group({y: "p50"}, {x, y, stroke, strokeOpacity, strokeWidth, sort, ...options})),
  10170. dot(data, map({y: oqr}, {x, y, z: x, r, stroke, strokeOpacity, ...options}))
  10171. );
  10172. }
  10173. // A map function that returns only outliers, returning NaN for non-outliers
  10174. function oqr(values) {
  10175. const r1 = loqr1(values);
  10176. const r2 = hiqr2(values);
  10177. return values.map((v) => (v < r1 || v > r2 ? v : NaN));
  10178. }
  10179. function loqr1(values) {
  10180. const lo = quartile1(values) * 2.5 - quartile3(values) * 1.5;
  10181. return d3.min(values, (d) => (d >= lo ? d : NaN));
  10182. }
  10183. function hiqr2(values) {
  10184. const hi = quartile3(values) * 2.5 - quartile1(values) * 1.5;
  10185. return d3.max(values, (d) => (d <= hi ? d : NaN));
  10186. }
  10187. function quartile1(values) {
  10188. return d3.quantile(values, 0.25);
  10189. }
  10190. function quartile3(values) {
  10191. return d3.quantile(values, 0.75);
  10192. }
  10193. const defaults$6 = {
  10194. ariaLabel: "raster",
  10195. stroke: null,
  10196. pixelSize: 1
  10197. };
  10198. function number(input, name) {
  10199. const x = +input;
  10200. if (isNaN(x)) throw new Error(`invalid ${name}: ${input}`);
  10201. return x;
  10202. }
  10203. function integer(input, name) {
  10204. const x = Math.floor(input);
  10205. if (isNaN(x)) throw new Error(`invalid ${name}: ${input}`);
  10206. return x;
  10207. }
  10208. class AbstractRaster extends Mark {
  10209. constructor(data, channels, options = {}, defaults) {
  10210. let {
  10211. width,
  10212. height,
  10213. x,
  10214. y,
  10215. x1 = x == null ? 0 : undefined,
  10216. y1 = y == null ? 0 : undefined,
  10217. x2 = x == null ? width : undefined,
  10218. y2 = y == null ? height : undefined,
  10219. pixelSize = defaults.pixelSize,
  10220. blur = 0,
  10221. interpolate
  10222. } = options;
  10223. if (width != null) width = integer(width, "width");
  10224. if (height != null) height = integer(height, "height");
  10225. // These represent the (minimum) bounds of the raster; they are not
  10226. // evaluated for each datum. Also, if x and y are not specified explicitly,
  10227. // then these bounds are used to compute the dense linear grid.
  10228. if (x1 != null) x1 = number(x1, "x1");
  10229. if (y1 != null) y1 = number(y1, "y1");
  10230. if (x2 != null) x2 = number(x2, "x2");
  10231. if (y2 != null) y2 = number(y2, "y2");
  10232. if (x == null && (x1 == null || x2 == null)) throw new Error("missing x");
  10233. if (y == null && (y1 == null || y2 == null)) throw new Error("missing y");
  10234. if (data != null && width != null && height != null) {
  10235. // If x and y are not given, assume the data is a dense array of samples
  10236. // covering the entire grid in row-major order. These defaults allow
  10237. // further shorthand where x and y represent grid column and row index.
  10238. // TODO If we know that the x and y scales are linear, then we could avoid
  10239. // materializing these columns to improve performance.
  10240. if (x === undefined && x1 != null && x2 != null) x = denseX(x1, x2, width);
  10241. if (y === undefined && y1 != null && y2 != null) y = denseY(y1, y2, width, height);
  10242. }
  10243. super(
  10244. data,
  10245. {
  10246. x: {value: x, scale: "x", optional: true},
  10247. y: {value: y, scale: "y", optional: true},
  10248. x1: {value: x1 == null ? null : [x1], scale: "x", optional: true, filter: null},
  10249. y1: {value: y1 == null ? null : [y1], scale: "y", optional: true, filter: null},
  10250. x2: {value: x2 == null ? null : [x2], scale: "x", optional: true, filter: null},
  10251. y2: {value: y2 == null ? null : [y2], scale: "y", optional: true, filter: null},
  10252. ...channels
  10253. },
  10254. options,
  10255. defaults
  10256. );
  10257. this.width = width;
  10258. this.height = height;
  10259. this.pixelSize = number(pixelSize, "pixelSize");
  10260. this.blur = number(blur, "blur");
  10261. this.interpolate = x == null || y == null ? null : maybeInterpolate(interpolate); // interpolation requires x & y
  10262. }
  10263. }
  10264. class Raster extends AbstractRaster {
  10265. constructor(data, options = {}) {
  10266. const {imageRendering} = options;
  10267. if (data == null) {
  10268. const {fill, fillOpacity} = options;
  10269. if (maybeNumberChannel(fillOpacity)[0] !== undefined) options = sampler("fillOpacity", options);
  10270. if (maybeColorChannel(fill)[0] !== undefined) options = sampler("fill", options);
  10271. }
  10272. super(data, undefined, options, defaults$6);
  10273. this.imageRendering = impliedString(imageRendering, "auto");
  10274. }
  10275. // Ignore the color scale, so the fill channel is returned unscaled.
  10276. scale(channels, {color, ...scales}, context) {
  10277. return super.scale(channels, scales, context);
  10278. }
  10279. render(index, scales, values, dimensions, context) {
  10280. const color = scales[values.channels.fill?.scale] ?? ((x) => x);
  10281. const {x: X, y: Y} = values;
  10282. const {document} = context;
  10283. const [x1, y1, x2, y2] = renderBounds(values, dimensions, context);
  10284. const dx = x2 - x1;
  10285. const dy = y2 - y1;
  10286. const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this;
  10287. const n = w * h;
  10288. // Interpolate the samples to fill the raster grid. If interpolate is null,
  10289. // then a continuous function is being sampled, and the raster grid is
  10290. // already aligned with the canvas.
  10291. let {fill: F, fillOpacity: FO} = values;
  10292. let offset = 0;
  10293. if (this.interpolate) {
  10294. const kx = w / dx;
  10295. const ky = h / dy;
  10296. const IX = map$1(X, (x) => (x - x1) * kx, Float64Array);
  10297. const IY = map$1(Y, (y) => (y - y1) * ky, Float64Array);
  10298. if (F) F = this.interpolate(index, w, h, IX, IY, F);
  10299. if (FO) FO = this.interpolate(index, w, h, IX, IY, FO);
  10300. }
  10301. // When faceting without interpolation, as when sampling a continuous
  10302. // function, offset into the dense grid based on the current facet index.
  10303. else if (this.data == null && index) offset = index.fi * n;
  10304. // Render the raster grid to the canvas, blurring if needed.
  10305. const canvas = document.createElement("canvas");
  10306. canvas.width = w;
  10307. canvas.height = h;
  10308. const context2d = canvas.getContext("2d");
  10309. const image = context2d.createImageData(w, h);
  10310. const imageData = image.data;
  10311. let {r, g, b} = d3.rgb(this.fill) ?? {r: 0, g: 0, b: 0};
  10312. let a = (this.fillOpacity ?? 1) * 255;
  10313. for (let i = 0; i < n; ++i) {
  10314. const j = i << 2;
  10315. if (F) {
  10316. const fi = color(F[i + offset]);
  10317. if (fi == null) {
  10318. imageData[j + 3] = 0;
  10319. continue;
  10320. }
  10321. ({r, g, b} = d3.rgb(fi));
  10322. }
  10323. if (FO) a = FO[i + offset] * 255;
  10324. imageData[j + 0] = r;
  10325. imageData[j + 1] = g;
  10326. imageData[j + 2] = b;
  10327. imageData[j + 3] = a;
  10328. }
  10329. if (this.blur > 0) d3.blurImage(image, this.blur);
  10330. context2d.putImageData(image, 0, 0);
  10331. return create("svg:g", context)
  10332. .call(applyIndirectStyles, this, dimensions, context)
  10333. .call(applyTransform, this, scales)
  10334. .call((g) =>
  10335. g
  10336. .append("image")
  10337. .attr("transform", `translate(${x1},${y1}) scale(${Math.sign(x2 - x1)},${Math.sign(y2 - y1)})`)
  10338. .attr("width", Math.abs(dx))
  10339. .attr("height", Math.abs(dy))
  10340. .attr("preserveAspectRatio", "none")
  10341. .call(applyAttr, "image-rendering", this.imageRendering)
  10342. .call(applyDirectStyles, this)
  10343. .attr("xlink:href", canvas.toDataURL())
  10344. )
  10345. .node();
  10346. }
  10347. }
  10348. function maybeTuples(k, data, options) {
  10349. if (arguments.length < 3) (options = data), (data = null);
  10350. let {x, y, [k]: z, ...rest} = options;
  10351. // Because we use implicit x and y when z is a function of (x, y), and when
  10352. // data is a dense grid, we must further disambiguate by testing whether data
  10353. // contains [x, y, z?] tuples. Hence you can’t use this shorthand with a
  10354. // transform that lazily generates tuples, but that seems reasonable since
  10355. // this is just for convenience anyway.
  10356. if (x === undefined && y === undefined && isTuples(data)) {
  10357. (x = first), (y = second);
  10358. if (z === undefined) z = third;
  10359. }
  10360. return [data, {...rest, x, y, [k]: z}];
  10361. }
  10362. function raster() {
  10363. const [data, options] = maybeTuples("fill", ...arguments);
  10364. return new Raster(
  10365. data,
  10366. data == null || options.fill !== undefined || options.fillOpacity !== undefined
  10367. ? options
  10368. : {...options, fill: identity$1}
  10369. );
  10370. }
  10371. // See rasterBounds; this version is called during render.
  10372. function renderBounds({x1, y1, x2, y2}, dimensions, {projection}) {
  10373. const {width, height, marginTop, marginRight, marginBottom, marginLeft} = dimensions;
  10374. return [
  10375. x1 && projection == null ? x1[0] : marginLeft,
  10376. y1 && projection == null ? y1[0] : marginTop,
  10377. x2 && projection == null ? x2[0] : width - marginRight,
  10378. y2 && projection == null ? y2[0] : height - marginBottom
  10379. ];
  10380. }
  10381. // If x1, y1, x2, y2 were specified, and no projection is in use (and thus the
  10382. // raster grid is necessarily an axis-aligned rectangle), then we can compute
  10383. // tighter bounds for the image, improving resolution.
  10384. function rasterBounds({x1, y1, x2, y2}, scales, dimensions, context) {
  10385. const channels = {};
  10386. if (x1) channels.x1 = x1;
  10387. if (y1) channels.y1 = y1;
  10388. if (x2) channels.x2 = x2;
  10389. if (y2) channels.y2 = y2;
  10390. return renderBounds(valueObject(channels, scales), dimensions, context);
  10391. }
  10392. // Evaluates the function with the given name, if it exists, on the raster grid,
  10393. // generating a channel of the same name.
  10394. function sampler(name, options = {}) {
  10395. const {[name]: value} = options;
  10396. if (typeof value !== "function") throw new Error(`invalid ${name}: not a function`);
  10397. return initializer({...options, [name]: undefined}, function (data, facets, channels, scales, dimensions, context) {
  10398. const {x, y} = scales;
  10399. // TODO Allow projections, if invertible.
  10400. if (!x) throw new Error("missing scale: x");
  10401. if (!y) throw new Error("missing scale: y");
  10402. const [x1, y1, x2, y2] = rasterBounds(channels, scales, dimensions, context);
  10403. const dx = x2 - x1;
  10404. const dy = y2 - y1;
  10405. const {pixelSize: k} = this;
  10406. // Note: this must exactly match the defaults in render above!
  10407. const {width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = options;
  10408. // TODO Hint to use a typed array when possible?
  10409. const V = new Array(w * h * (facets ? facets.length : 1));
  10410. const kx = dx / w;
  10411. const ky = dy / h;
  10412. let i = 0;
  10413. for (const facet of facets ?? [undefined]) {
  10414. for (let yi = 0.5; yi < h; ++yi) {
  10415. for (let xi = 0.5; xi < w; ++xi, ++i) {
  10416. V[i] = value(x.invert(x1 + xi * kx), y.invert(y1 + yi * ky), facet);
  10417. }
  10418. }
  10419. }
  10420. return {data: V, facets, channels: {[name]: {value: V, scale: "auto"}}};
  10421. });
  10422. }
  10423. function maybeInterpolate(interpolate) {
  10424. if (typeof interpolate === "function") return interpolate;
  10425. if (interpolate == null) return interpolateNone;
  10426. switch (`${interpolate}`.toLowerCase()) {
  10427. case "none":
  10428. return interpolateNone;
  10429. case "nearest":
  10430. return interpolateNearest;
  10431. case "barycentric":
  10432. return interpolatorBarycentric();
  10433. case "random-walk":
  10434. return interpolatorRandomWalk();
  10435. }
  10436. throw new Error(`invalid interpolate: ${interpolate}`);
  10437. }
  10438. // Applies a simple forward mapping of samples, binning them into pixels without
  10439. // any blending or interpolation. Note: if multiple samples map to the same
  10440. // pixel, the last one wins; this can introduce bias if the points are not in
  10441. // random order, so use Plot.shuffle to randomize the input if needed.
  10442. function interpolateNone(index, width, height, X, Y, V) {
  10443. const W = new Array(width * height);
  10444. for (const i of index) {
  10445. if (X[i] < 0 || X[i] >= width || Y[i] < 0 || Y[i] >= height) continue;
  10446. W[Math.floor(Y[i]) * width + Math.floor(X[i])] = V[i];
  10447. }
  10448. return W;
  10449. }
  10450. function interpolatorBarycentric({random = d3.randomLcg(42)} = {}) {
  10451. return (index, width, height, X, Y, V) => {
  10452. // Interpolate the interior of all triangles with barycentric coordinates
  10453. const {points, triangles, hull} = d3.Delaunay.from(
  10454. index,
  10455. (i) => X[i],
  10456. (i) => Y[i]
  10457. );
  10458. const W = new V.constructor(width * height).fill(NaN);
  10459. const S = new Uint8Array(width * height); // 1 if pixel has been seen.
  10460. const mix = mixer(V, random);
  10461. for (let i = 0; i < triangles.length; i += 3) {
  10462. const ta = triangles[i];
  10463. const tb = triangles[i + 1];
  10464. const tc = triangles[i + 2];
  10465. const Ax = points[2 * ta];
  10466. const Bx = points[2 * tb];
  10467. const Cx = points[2 * tc];
  10468. const Ay = points[2 * ta + 1];
  10469. const By = points[2 * tb + 1];
  10470. const Cy = points[2 * tc + 1];
  10471. const x1 = Math.min(Ax, Bx, Cx);
  10472. const x2 = Math.max(Ax, Bx, Cx);
  10473. const y1 = Math.min(Ay, By, Cy);
  10474. const y2 = Math.max(Ay, By, Cy);
  10475. const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
  10476. if (!z) continue;
  10477. const va = V[index[ta]];
  10478. const vb = V[index[tb]];
  10479. const vc = V[index[tc]];
  10480. for (let x = Math.floor(x1); x < x2; ++x) {
  10481. for (let y = Math.floor(y1); y < y2; ++y) {
  10482. if (x < 0 || x >= width || y < 0 || y >= height) continue;
  10483. const xp = x + 0.5; // sample pixel centroids
  10484. const yp = y + 0.5;
  10485. const s = Math.sign(z);
  10486. const ga = (By - Cy) * (xp - Cx) + (yp - Cy) * (Cx - Bx);
  10487. if (ga * s < 0) continue;
  10488. const gb = (Cy - Ay) * (xp - Cx) + (yp - Cy) * (Ax - Cx);
  10489. if (gb * s < 0) continue;
  10490. const gc = z - (ga + gb);
  10491. if (gc * s < 0) continue;
  10492. const i = x + width * y;
  10493. W[i] = mix(va, ga / z, vb, gb / z, vc, gc / z, x, y);
  10494. S[i] = 1;
  10495. }
  10496. }
  10497. }
  10498. extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix);
  10499. return W;
  10500. };
  10501. }
  10502. // Extrapolate by finding the closest point on the hull.
  10503. function extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix) {
  10504. X = Float64Array.from(hull, (i) => X[index[i]]);
  10505. Y = Float64Array.from(hull, (i) => Y[index[i]]);
  10506. V = Array.from(hull, (i) => V[index[i]]);
  10507. const n = X.length;
  10508. const rays = Array.from({length: n}, (_, j) => ray(j, X, Y));
  10509. let k = 0;
  10510. for (let y = 0; y < height; ++y) {
  10511. const yp = y + 0.5;
  10512. for (let x = 0; x < width; ++x) {
  10513. const i = x + width * y;
  10514. if (!S[i]) {
  10515. const xp = x + 0.5;
  10516. for (let l = 0; l < n; ++l) {
  10517. const j = (n + k + (l % 2 ? (l + 1) / 2 : -l / 2)) % n;
  10518. if (rays[j](xp, yp)) {
  10519. const t = segmentProject(X.at(j - 1), Y.at(j - 1), X[j], Y[j], xp, yp);
  10520. W[i] = mix(V.at(j - 1), t, V[j], 1 - t, V[j], 0, x, y);
  10521. k = j;
  10522. break;
  10523. }
  10524. }
  10525. }
  10526. }
  10527. }
  10528. }
  10529. // Projects a point p = [x, y] onto the line segment [p1, p2], returning the
  10530. // projected coordinates p’ as t in [0, 1] with p’ = t p1 + (1 - t) p2.
  10531. function segmentProject(x1, y1, x2, y2, x, y) {
  10532. const dx = x2 - x1;
  10533. const dy = y2 - y1;
  10534. const a = dx * (x2 - x) + dy * (y2 - y);
  10535. const b = dx * (x - x1) + dy * (y - y1);
  10536. return a > 0 && b > 0 ? a / (a + b) : +(a > b);
  10537. }
  10538. function cross(xa, ya, xb, yb) {
  10539. return xa * yb - xb * ya;
  10540. }
  10541. function ray(j, X, Y) {
  10542. const n = X.length;
  10543. const xc = X.at(j - 2);
  10544. const yc = Y.at(j - 2);
  10545. const xa = X.at(j - 1);
  10546. const ya = Y.at(j - 1);
  10547. const xb = X[j];
  10548. const yb = Y[j];
  10549. const xd = X.at(j + 1 - n);
  10550. const yd = Y.at(j + 1 - n);
  10551. const dxab = xa - xb;
  10552. const dyab = ya - yb;
  10553. const dxca = xc - xa;
  10554. const dyca = yc - ya;
  10555. const dxbd = xb - xd;
  10556. const dybd = yb - yd;
  10557. const hab = Math.hypot(dxab, dyab);
  10558. const hca = Math.hypot(dxca, dyca);
  10559. const hbd = Math.hypot(dxbd, dybd);
  10560. return (x, y) => {
  10561. const dxa = x - xa;
  10562. const dya = y - ya;
  10563. const dxb = x - xb;
  10564. const dyb = y - yb;
  10565. return (
  10566. cross(dxa, dya, dxb, dyb) > -1e-6 &&
  10567. cross(dxa, dya, dxab, dyab) * hca - cross(dxa, dya, dxca, dyca) * hab > -1e-6 &&
  10568. cross(dxb, dyb, dxbd, dybd) * hab - cross(dxb, dyb, dxab, dyab) * hbd <= 0
  10569. );
  10570. };
  10571. }
  10572. function interpolateNearest(index, width, height, X, Y, V) {
  10573. const W = new V.constructor(width * height);
  10574. const delaunay = d3.Delaunay.from(
  10575. index,
  10576. (i) => X[i],
  10577. (i) => Y[i]
  10578. );
  10579. // memoization of delaunay.find for the line start (iy) and pixel (ix)
  10580. let iy, ix;
  10581. for (let y = 0.5, k = 0; y < height; ++y) {
  10582. ix = iy;
  10583. for (let x = 0.5; x < width; ++x, ++k) {
  10584. ix = delaunay.find(x, y, ix);
  10585. if (x === 0.5) iy = ix;
  10586. W[k] = V[index[ix]];
  10587. }
  10588. }
  10589. return W;
  10590. }
  10591. // https://observablehq.com/@observablehq/walk-on-spheres-precision
  10592. function interpolatorRandomWalk({random = d3.randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) {
  10593. return (index, width, height, X, Y, V) => {
  10594. const W = new V.constructor(width * height);
  10595. const delaunay = d3.Delaunay.from(
  10596. index,
  10597. (i) => X[i],
  10598. (i) => Y[i]
  10599. );
  10600. // memoization of delaunay.find for the line start (iy), pixel (ix), and wos step (iw)
  10601. let iy, ix, iw;
  10602. for (let y = 0.5, k = 0; y < height; ++y) {
  10603. ix = iy;
  10604. for (let x = 0.5; x < width; ++x, ++k) {
  10605. let cx = x;
  10606. let cy = y;
  10607. iw = ix = delaunay.find(cx, cy, ix);
  10608. if (x === 0.5) iy = ix;
  10609. let distance; // distance to closest sample
  10610. let step = 0; // count of steps for this walk
  10611. while ((distance = Math.hypot(X[index[iw]] - cx, Y[index[iw]] - cy)) > minDistance && step < maxSteps) {
  10612. const angle = random(x, y, step) * 2 * Math.PI;
  10613. cx += Math.cos(angle) * distance;
  10614. cy += Math.sin(angle) * distance;
  10615. iw = delaunay.find(cx, cy, iw);
  10616. ++step;
  10617. }
  10618. W[k] = V[index[iw]];
  10619. }
  10620. }
  10621. return W;
  10622. };
  10623. }
  10624. function blend(a, ca, b, cb, c, cc) {
  10625. return ca * a + cb * b + cc * c;
  10626. }
  10627. function pick(random) {
  10628. return (a, ca, b, cb, c, cc, x, y) => {
  10629. const u = random(x, y);
  10630. return u < ca ? a : u < ca + cb ? b : c;
  10631. };
  10632. }
  10633. function mixer(F, random) {
  10634. return isNumeric(F) || isTemporal(F) ? blend : pick(random);
  10635. }
  10636. function denseX(x1, x2, width) {
  10637. return {
  10638. transform(data) {
  10639. const n = data.length;
  10640. const X = new Float64Array(n);
  10641. const kx = (x2 - x1) / width;
  10642. const x0 = x1 + kx / 2;
  10643. for (let i = 0; i < n; ++i) X[i] = (i % width) * kx + x0;
  10644. return X;
  10645. }
  10646. };
  10647. }
  10648. function denseY(y1, y2, width, height) {
  10649. return {
  10650. transform(data) {
  10651. const n = data.length;
  10652. const Y = new Float64Array(n);
  10653. const ky = (y2 - y1) / height;
  10654. const y0 = y1 + ky / 2;
  10655. for (let i = 0; i < n; ++i) Y[i] = (Math.floor(i / width) % height) * ky + y0;
  10656. return Y;
  10657. }
  10658. };
  10659. }
  10660. const defaults$5 = {
  10661. ariaLabel: "contour",
  10662. fill: "none",
  10663. stroke: "currentColor",
  10664. strokeMiterlimit: 1,
  10665. pixelSize: 2
  10666. };
  10667. class Contour extends AbstractRaster {
  10668. constructor(data, {smooth = true, value, ...options} = {}) {
  10669. const channels = styles({}, options, defaults$5);
  10670. // If value is not specified explicitly, look for a channel to promote. If
  10671. // more than one channel is present, throw an error. (To disambiguate,
  10672. // specify the value option explicitly.)
  10673. if (value === undefined) {
  10674. for (const key in channels) {
  10675. if (channels[key].value != null) {
  10676. if (value !== undefined) throw new Error("ambiguous contour value");
  10677. value = options[key];
  10678. options[key] = "value";
  10679. }
  10680. }
  10681. }
  10682. // For any channel specified as the literal (contour threshold) "value"
  10683. // (maybe because of the promotion above), propagate the label from the
  10684. // original value definition.
  10685. if (value != null) {
  10686. const v = {transform: (D) => D.map((d) => d.value), label: labelof(value)};
  10687. for (const key in channels) {
  10688. if (options[key] === "value") {
  10689. options[key] = v;
  10690. }
  10691. }
  10692. }
  10693. // If the data is null, then we’ll construct the raster grid by evaluating a
  10694. // function for each point in a dense grid. The value channel is populated
  10695. // by the sampler initializer, and hence is not passed to super to avoid
  10696. // computing it before there’s data.
  10697. if (data == null) {
  10698. if (value == null) throw new Error("missing contour value");
  10699. options = sampler("value", {value, ...options});
  10700. value = null;
  10701. }
  10702. // Otherwise if data was provided, it represents a discrete set of spatial
  10703. // samples (often a grid, but not necessarily). If no interpolation method
  10704. // was specified, default to nearest.
  10705. else {
  10706. let {interpolate} = options;
  10707. if (value === undefined) value = identity$1;
  10708. if (interpolate === undefined) options.interpolate = "nearest";
  10709. }
  10710. // Wrap the options in our initializer that computes the contour geometries;
  10711. // this runs after any other initializers (and transforms).
  10712. super(data, {value: {value, optional: true}}, contourGeometry(options), defaults$5);
  10713. // With the exception of the x, y, x1, y1, x2, y2, and value channels, this
  10714. // mark’s channels are not evaluated on the initial data but rather on the
  10715. // contour multipolygons generated in the initializer.
  10716. const contourChannels = {geometry: {value: identity$1}};
  10717. for (const key in this.channels) {
  10718. const channel = this.channels[key];
  10719. const {scale} = channel;
  10720. if (scale === "x" || scale === "y" || key === "value") continue;
  10721. contourChannels[key] = channel;
  10722. delete this.channels[key];
  10723. }
  10724. this.contourChannels = contourChannels;
  10725. this.smooth = !!smooth;
  10726. }
  10727. filter(index, {x, y, value, ...channels}, values) {
  10728. // Only filter channels constructed by the contourGeometry initializer; the
  10729. // x, y, and value channels must be filtered by the initializer itself.
  10730. return super.filter(index, channels, values);
  10731. }
  10732. render(index, scales, channels, dimensions, context) {
  10733. const {geometry: G} = channels;
  10734. const path = d3.geoPath();
  10735. return create("svg:g", context)
  10736. .call(applyIndirectStyles, this, dimensions, context)
  10737. .call(applyTransform, this, scales)
  10738. .call((g) => {
  10739. g.selectAll()
  10740. .data(index)
  10741. .enter()
  10742. .append("path")
  10743. .call(applyDirectStyles, this)
  10744. .attr("d", (i) => path(G[i]))
  10745. .call(applyChannelStyles, this, channels);
  10746. })
  10747. .node();
  10748. }
  10749. }
  10750. function contourGeometry({thresholds, interval, ...options}) {
  10751. thresholds = maybeThresholds(thresholds, interval, d3.thresholdSturges);
  10752. return initializer(options, function (data, facets, channels, scales, dimensions, context) {
  10753. const [x1, y1, x2, y2] = rasterBounds(channels, scales, dimensions, context);
  10754. const dx = x2 - x1;
  10755. const dy = y2 - y1;
  10756. const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this;
  10757. const kx = w / dx;
  10758. const ky = h / dy;
  10759. const V = channels.value.value;
  10760. const VV = []; // V per facet
  10761. // Interpolate the raster grid, as needed.
  10762. if (this.interpolate) {
  10763. const {x: X, y: Y} = applyPosition(channels, scales, context);
  10764. // Convert scaled (screen) coordinates to grid (canvas) coordinates.
  10765. const IX = map$1(X, (x) => (x - x1) * kx, Float64Array);
  10766. const IY = map$1(Y, (y) => (y - y1) * ky, Float64Array);
  10767. // The contour mark normally skips filtering on x, y, and value, so here
  10768. // we’re careful to use different names (0, 1, 2) when filtering.
  10769. const ichannels = [channels.x, channels.y, channels.value];
  10770. const ivalues = [IX, IY, V];
  10771. for (const facet of facets) {
  10772. const index = this.filter(facet, ichannels, ivalues);
  10773. VV.push(this.interpolate(index, w, h, IX, IY, V));
  10774. }
  10775. }
  10776. // Otherwise, chop up the existing dense raster grid into facets, if needed.
  10777. // V must be a dense grid in projected coordinates; if there are multiple
  10778. // facets, then V must be laid out vertically as facet 0, 1, 2… etc.
  10779. else if (facets) {
  10780. const n = w * h;
  10781. const m = facets.length;
  10782. for (let i = 0; i < m; ++i) VV.push(V.slice(i * n, i * n + n));
  10783. } else {
  10784. VV.push(V);
  10785. }
  10786. // Blur the raster grid, if desired.
  10787. if (this.blur > 0) for (const V of VV) d3.blur2({data: V, width: w, height: h}, this.blur);
  10788. // Compute the contour thresholds.
  10789. const T = maybeTicks(thresholds, V, ...finiteExtent(VV));
  10790. if (T === null) throw new Error(`unsupported thresholds: ${thresholds}`);
  10791. // Compute the (maybe faceted) contours.
  10792. const {contour} = d3.contours().size([w, h]).smooth(this.smooth);
  10793. const contourData = [];
  10794. const contourFacets = [];
  10795. for (const V of VV) {
  10796. contourFacets.push(d3.range(contourData.length, contourData.push(...map$1(T, (t) => contour(V, t)))));
  10797. }
  10798. // Rescale the contour multipolygon from grid to screen coordinates.
  10799. for (const {coordinates} of contourData) {
  10800. for (const rings of coordinates) {
  10801. for (const ring of rings) {
  10802. for (const point of ring) {
  10803. point[0] = point[0] / kx + x1;
  10804. point[1] = point[1] / ky + y1;
  10805. }
  10806. }
  10807. }
  10808. }
  10809. // Compute the deferred channels.
  10810. return {
  10811. data: contourData,
  10812. facets: contourFacets,
  10813. channels: createChannels(this.contourChannels, contourData)
  10814. };
  10815. });
  10816. }
  10817. // Apply the thresholds interval, function, or count, and return an array of
  10818. // ticks. d3-contour unlike d3-array doesn’t pass the min and max automatically,
  10819. // so we do that here to normalize, and also so we can share consistent
  10820. // thresholds across facets. When an interval is used, note that the lowest
  10821. // threshold should be below (or equal) to the lowest value, or else some data
  10822. // will be missing.
  10823. function maybeTicks(thresholds, V, min, max) {
  10824. if (typeof thresholds?.range === "function") return thresholds.range(thresholds.floor(min), max);
  10825. if (typeof thresholds === "function") thresholds = thresholds(V, min, max);
  10826. if (typeof thresholds !== "number") return arrayify(thresholds);
  10827. const tz = d3.ticks(...d3.nice(min, max, thresholds), thresholds);
  10828. while (tz[tz.length - 1] >= max) tz.pop();
  10829. while (tz[1] < min) tz.shift();
  10830. return tz;
  10831. }
  10832. function contour() {
  10833. return new Contour(...maybeTuples("value", ...arguments));
  10834. }
  10835. function finiteExtent(VV) {
  10836. return [d3.min(VV, (V) => d3.min(V, finite)), d3.max(VV, (V) => d3.max(V, finite))];
  10837. }
  10838. function finite(x) {
  10839. return isFinite(x) ? x : NaN;
  10840. }
  10841. function crosshair(data, options) {
  10842. return crosshairK(pointer, data, options);
  10843. }
  10844. function crosshairX(data, options = {}) {
  10845. return crosshairK(pointerX, data, options);
  10846. }
  10847. function crosshairY(data, options = {}) {
  10848. return crosshairK(pointerY, data, options);
  10849. }
  10850. function crosshairK(pointer, data, options = {}) {
  10851. const {x, y, maxRadius} = options;
  10852. const p = pointer({px: x, py: y, maxRadius});
  10853. const M = [];
  10854. if (x != null) M.push(ruleX(data, ruleOptions("x", {...p, inset: -6}, options)));
  10855. if (y != null) M.push(ruleY(data, ruleOptions("y", {...p, inset: -6}, options)));
  10856. if (x != null) M.push(text(data, textOptions("x", {...p, dy: 9, frameAnchor: "bottom", lineAnchor: "top"}, options)));
  10857. if (y != null) M.push(text(data, textOptions("y", {...p, dx: -9, frameAnchor: "left", textAnchor: "end"}, options)));
  10858. for (const m of M) m.ariaLabel = `crosshair ${m.ariaLabel}`;
  10859. return marks(...M);
  10860. }
  10861. function markOptions(
  10862. k,
  10863. {channels: pointerChannels, ...pointerOptions},
  10864. {facet, facetAnchor, fx, fy, [k]: p, channels, transform, initializer}
  10865. ) {
  10866. return {
  10867. ...pointerOptions,
  10868. facet,
  10869. facetAnchor,
  10870. fx,
  10871. fy,
  10872. [k]: p,
  10873. channels: {...pointerChannels, ...channels},
  10874. transform,
  10875. initializer: pxpy(k, initializer)
  10876. };
  10877. }
  10878. // Wrap the initializer, if any, mapping px and py to x and y temporarily (e.g.,
  10879. // for hexbin) then mapping back to px and py for rendering.
  10880. function pxpy(k, i) {
  10881. if (i == null) return i;
  10882. return function (data, facets, {x: x1, y: y1, px, py, ...c1}, ...args) {
  10883. const {channels: {x, y, ...c} = {}, ...rest} = i.call(this, data, facets, {...c1, x: px, y: py}, ...args);
  10884. return {
  10885. channels: {
  10886. ...c,
  10887. ...(x && {px: x, ...(k === "x" && {x})}),
  10888. ...(y && {py: y, ...(k === "y" && {y})})
  10889. },
  10890. ...rest
  10891. };
  10892. };
  10893. }
  10894. function ruleOptions(k, pointerOptions, options) {
  10895. const {
  10896. color = "currentColor",
  10897. opacity = 0.2,
  10898. ruleStroke: stroke = color,
  10899. ruleStrokeOpacity: strokeOpacity = opacity,
  10900. ruleStrokeWidth: strokeWidth
  10901. } = options;
  10902. return {
  10903. ...markOptions(k, pointerOptions, options),
  10904. stroke,
  10905. strokeOpacity,
  10906. strokeWidth
  10907. };
  10908. }
  10909. function textOptions(k, pointerOptions, options) {
  10910. const {
  10911. color = "currentColor",
  10912. textFill: fill = color,
  10913. textFillOpacity: fillOpacity,
  10914. textStroke: stroke = "var(--plot-background)",
  10915. textStrokeOpacity: strokeOpacity,
  10916. textStrokeWidth: strokeWidth = 5
  10917. } = options;
  10918. return {
  10919. ...markOptions(k, pointerOptions, textChannel(k, options)),
  10920. fill,
  10921. fillOpacity,
  10922. stroke,
  10923. strokeOpacity,
  10924. strokeWidth
  10925. };
  10926. }
  10927. // Rather than aliasing text to have the same definition as x and y, we use an
  10928. // initializer to alias the channel values, such that the text channel can be
  10929. // derived by an initializer such as hexbin.
  10930. function textChannel(source, options) {
  10931. return initializer(options, (data, facets, channels) => {
  10932. return {channels: {text: {value: getSource(channels, source)?.value}}};
  10933. });
  10934. }
  10935. const delaunayLinkDefaults = {
  10936. ariaLabel: "delaunay link",
  10937. fill: "none",
  10938. stroke: "currentColor",
  10939. strokeMiterlimit: 1
  10940. };
  10941. const delaunayMeshDefaults = {
  10942. ariaLabel: "delaunay mesh",
  10943. fill: null,
  10944. stroke: "currentColor",
  10945. strokeOpacity: 0.2
  10946. };
  10947. const hullDefaults = {
  10948. ariaLabel: "hull",
  10949. fill: "none",
  10950. stroke: "currentColor",
  10951. strokeWidth: 1.5,
  10952. strokeMiterlimit: 1
  10953. };
  10954. const voronoiDefaults = {
  10955. ariaLabel: "voronoi",
  10956. fill: "none",
  10957. stroke: "currentColor",
  10958. strokeMiterlimit: 1
  10959. };
  10960. const voronoiMeshDefaults = {
  10961. ariaLabel: "voronoi mesh",
  10962. fill: null,
  10963. stroke: "currentColor",
  10964. strokeOpacity: 0.2
  10965. };
  10966. class DelaunayLink extends Mark {
  10967. constructor(data, options = {}) {
  10968. const {x, y, z, curve, tension} = options;
  10969. super(
  10970. data,
  10971. {
  10972. x: {value: x, scale: "x", optional: true},
  10973. y: {value: y, scale: "y", optional: true},
  10974. z: {value: z, optional: true}
  10975. },
  10976. options,
  10977. delaunayLinkDefaults
  10978. );
  10979. this.curve = maybeCurve(curve, tension);
  10980. markers(this, options);
  10981. }
  10982. render(index, scales, channels, dimensions, context) {
  10983. const {x, y} = scales;
  10984. const {x: X, y: Y, z: Z} = channels;
  10985. const {curve} = this;
  10986. const [cx, cy] = applyFrameAnchor(this, dimensions);
  10987. const xi = X ? (i) => X[i] : constant(cx);
  10988. const yi = Y ? (i) => Y[i] : constant(cy);
  10989. const mark = this;
  10990. function links(index) {
  10991. let i = -1;
  10992. const newIndex = [];
  10993. const newChannels = {};
  10994. for (const k in channels) newChannels[k] = [];
  10995. const X1 = [];
  10996. const X2 = [];
  10997. const Y1 = [];
  10998. const Y2 = [];
  10999. function link(ti, tj) {
  11000. ti = index[ti];
  11001. tj = index[tj];
  11002. newIndex.push(++i);
  11003. X1[i] = xi(ti);
  11004. Y1[i] = yi(ti);
  11005. X2[i] = xi(tj);
  11006. Y2[i] = yi(tj);
  11007. for (const k in channels) newChannels[k].push(channels[k][tj]);
  11008. }
  11009. const {halfedges, hull, triangles} = d3.Delaunay.from(index, xi, yi);
  11010. for (let i = 0; i < halfedges.length; ++i) {
  11011. // inner edges
  11012. const j = halfedges[i];
  11013. if (j > i) link(triangles[i], triangles[j]);
  11014. }
  11015. for (let i = 0; i < hull.length; ++i) {
  11016. // convex hull
  11017. link(hull[i], hull[(i + 1) % hull.length]);
  11018. }
  11019. d3.select(this)
  11020. .selectAll()
  11021. .data(newIndex)
  11022. .enter()
  11023. .append("path")
  11024. .call(applyDirectStyles, mark)
  11025. .attr("d", (i) => {
  11026. const p = d3.pathRound();
  11027. const c = curve(p);
  11028. c.lineStart();
  11029. c.point(X1[i], Y1[i]);
  11030. c.point(X2[i], Y2[i]);
  11031. c.lineEnd();
  11032. return p;
  11033. })
  11034. .call(applyChannelStyles, mark, newChannels)
  11035. .call(applyMarkers, mark, newChannels, context);
  11036. }
  11037. return create("svg:g", context)
  11038. .call(applyIndirectStyles, this, dimensions, context)
  11039. .call(applyTransform, this, {x: X && x, y: Y && y})
  11040. .call(
  11041. Z
  11042. ? (g) =>
  11043. g
  11044. .selectAll()
  11045. .data(d3.group(index, (i) => Z[i]).values())
  11046. .enter()
  11047. .append("g")
  11048. .each(links)
  11049. : (g) => g.datum(index).each(links)
  11050. )
  11051. .node();
  11052. }
  11053. }
  11054. class AbstractDelaunayMark extends Mark {
  11055. constructor(data, options = {}, defaults, zof = ({z}) => z) {
  11056. const {x, y} = options;
  11057. super(
  11058. data,
  11059. {
  11060. x: {value: x, scale: "x", optional: true},
  11061. y: {value: y, scale: "y", optional: true},
  11062. z: {value: zof(options), optional: true}
  11063. },
  11064. options,
  11065. defaults
  11066. );
  11067. }
  11068. render(index, scales, channels, dimensions, context) {
  11069. const {x, y} = scales;
  11070. const {x: X, y: Y, z: Z} = channels;
  11071. const [cx, cy] = applyFrameAnchor(this, dimensions);
  11072. const xi = X ? (i) => X[i] : constant(cx);
  11073. const yi = Y ? (i) => Y[i] : constant(cy);
  11074. const mark = this;
  11075. function mesh(index) {
  11076. const delaunay = d3.Delaunay.from(index, xi, yi);
  11077. d3.select(this)
  11078. .append("path")
  11079. .datum(index[0])
  11080. .call(applyDirectStyles, mark)
  11081. .attr("d", mark._render(delaunay, dimensions))
  11082. .call(applyChannelStyles, mark, channels);
  11083. }
  11084. return create("svg:g", context)
  11085. .call(applyIndirectStyles, this, dimensions, context)
  11086. .call(applyTransform, this, {x: X && x, y: Y && y})
  11087. .call(
  11088. Z
  11089. ? (g) =>
  11090. g
  11091. .selectAll()
  11092. .data(d3.group(index, (i) => Z[i]).values())
  11093. .enter()
  11094. .append("g")
  11095. .each(mesh)
  11096. : (g) => g.datum(index).each(mesh)
  11097. )
  11098. .node();
  11099. }
  11100. }
  11101. class DelaunayMesh extends AbstractDelaunayMark {
  11102. constructor(data, options = {}) {
  11103. super(data, options, delaunayMeshDefaults);
  11104. this.fill = "none";
  11105. }
  11106. _render(delaunay) {
  11107. return delaunay.render();
  11108. }
  11109. }
  11110. class Hull extends AbstractDelaunayMark {
  11111. constructor(data, options = {}) {
  11112. super(data, options, hullDefaults, maybeZ);
  11113. }
  11114. _render(delaunay) {
  11115. return delaunay.renderHull();
  11116. }
  11117. }
  11118. class Voronoi extends Mark {
  11119. constructor(data, options = {}) {
  11120. const {x, y, z} = options;
  11121. super(
  11122. data,
  11123. {
  11124. x: {value: x, scale: "x", optional: true},
  11125. y: {value: y, scale: "y", optional: true},
  11126. z: {value: z, optional: true}
  11127. },
  11128. initializer(options, function (data, facets, channels, scales, dimensions, context) {
  11129. let {x: X, y: Y, z: Z} = channels;
  11130. ({x: X, y: Y} = applyPosition(channels, scales, context));
  11131. Z = Z?.value;
  11132. const C = new Array((X ?? Y).length).fill(null);
  11133. const [cx, cy] = applyFrameAnchor(this, dimensions);
  11134. const xi = X ? (i) => X[i] : constant(cx);
  11135. const yi = Y ? (i) => Y[i] : constant(cy);
  11136. for (let I of facets) {
  11137. if (X) I = I.filter((i) => defined(xi(i)));
  11138. if (Y) I = I.filter((i) => defined(yi(i)));
  11139. for (const [, J] of maybeGroup(I, Z)) {
  11140. const delaunay = d3.Delaunay.from(J, xi, yi);
  11141. const voronoi = voronoiof(delaunay, dimensions);
  11142. for (let i = 0, n = J.length; i < n; ++i) {
  11143. C[J[i]] = voronoi.renderCell(i);
  11144. }
  11145. }
  11146. }
  11147. return {data, facets, channels: {cells: {value: C}}};
  11148. }),
  11149. voronoiDefaults
  11150. );
  11151. }
  11152. render(index, scales, channels, dimensions, context) {
  11153. const {x, y} = scales;
  11154. const {x: X, y: Y, cells: C} = channels;
  11155. return create("svg:g", context)
  11156. .call(applyIndirectStyles, this, dimensions, context)
  11157. .call(applyTransform, this, {x: X && x, y: Y && y})
  11158. .call((g) => {
  11159. g.selectAll()
  11160. .data(index)
  11161. .enter()
  11162. .append("path")
  11163. .call(applyDirectStyles, this)
  11164. .attr("d", (i) => C[i])
  11165. .call(applyChannelStyles, this, channels);
  11166. })
  11167. .node();
  11168. }
  11169. }
  11170. class VoronoiMesh extends AbstractDelaunayMark {
  11171. constructor(data, options) {
  11172. super(data, options, voronoiMeshDefaults);
  11173. this.fill = "none";
  11174. }
  11175. _render(delaunay, dimensions) {
  11176. return voronoiof(delaunay, dimensions).render();
  11177. }
  11178. }
  11179. function voronoiof(delaunay, dimensions) {
  11180. const {width, height, marginTop, marginRight, marginBottom, marginLeft} = dimensions;
  11181. return delaunay.voronoi([marginLeft, marginTop, width - marginRight, height - marginBottom]);
  11182. }
  11183. function delaunayMark(DelaunayMark, data, {x, y, ...options} = {}) {
  11184. [x, y] = maybeTuple(x, y);
  11185. return new DelaunayMark(data, {...options, x, y});
  11186. }
  11187. function delaunayLink(data, options) {
  11188. return delaunayMark(DelaunayLink, data, options);
  11189. }
  11190. function delaunayMesh(data, options) {
  11191. return delaunayMark(DelaunayMesh, data, options);
  11192. }
  11193. function hull(data, options) {
  11194. return delaunayMark(Hull, data, options);
  11195. }
  11196. function voronoi(data, {x, y, initializer, ...options} = {}) {
  11197. return delaunayMark(Voronoi, data, {...basic({...options, x, y}, exclusiveFacets), initializer});
  11198. }
  11199. function voronoiMesh(data, options) {
  11200. return delaunayMark(VoronoiMesh, data, options);
  11201. }
  11202. const defaults$4 = {
  11203. ariaLabel: "density",
  11204. fill: "none",
  11205. stroke: "currentColor",
  11206. strokeMiterlimit: 1
  11207. };
  11208. class Density extends Mark {
  11209. constructor(data, {x, y, z, weight, fill, stroke, ...options} = {}) {
  11210. // If fill or stroke is specified as “density”, then temporarily treat these
  11211. // as a literal color when computing defaults and maybeZ; below, we’ll unset
  11212. // these constant colors back to undefined since they will instead be
  11213. // populated by a channel generated by the initializer.
  11214. const fillDensity = isDensity(fill) && ((fill = "currentColor"), true);
  11215. const strokeDensity = isDensity(stroke) && ((stroke = "currentColor"), true);
  11216. super(
  11217. data,
  11218. {
  11219. x: {value: x, scale: "x", optional: true},
  11220. y: {value: y, scale: "y", optional: true},
  11221. z: {value: maybeZ({z, fill, stroke}), optional: true},
  11222. weight: {value: weight, optional: true}
  11223. },
  11224. densityInitializer({...options, fill, stroke}, fillDensity, strokeDensity),
  11225. defaults$4
  11226. );
  11227. if (fillDensity) this.fill = undefined;
  11228. if (strokeDensity) this.stroke = undefined;
  11229. this.z = z;
  11230. }
  11231. filter(index) {
  11232. return index; // don’t filter contours constructed by initializer
  11233. }
  11234. render(index, scales, channels, dimensions, context) {
  11235. const {contours} = channels;
  11236. const path = d3.geoPath();
  11237. return create("svg:g", context)
  11238. .call(applyIndirectStyles, this, dimensions, context)
  11239. .call(applyTransform, this, {})
  11240. .call((g) =>
  11241. g
  11242. .selectAll()
  11243. .data(index)
  11244. .enter()
  11245. .append("path")
  11246. .call(applyDirectStyles, this)
  11247. .call(applyChannelStyles, this, channels)
  11248. .attr("d", (i) => path(contours[i]))
  11249. )
  11250. .node();
  11251. }
  11252. }
  11253. function density(data, {x, y, ...options} = {}) {
  11254. [x, y] = maybeTuple(x, y);
  11255. return new Density(data, {...options, x, y});
  11256. }
  11257. const dropChannels = new Set(["x", "y", "z", "weight"]);
  11258. function densityInitializer(options, fillDensity, strokeDensity) {
  11259. const k = 100; // arbitrary scale factor for readability
  11260. let {bandwidth, thresholds} = options;
  11261. bandwidth = bandwidth === undefined ? 20 : +bandwidth;
  11262. thresholds =
  11263. thresholds === undefined
  11264. ? 20
  11265. : typeof thresholds?.[Symbol.iterator] === "function"
  11266. ? coerceNumbers(thresholds)
  11267. : +thresholds;
  11268. return initializer(options, function (data, facets, channels, scales, dimensions, context) {
  11269. const W = channels.weight ? coerceNumbers(channels.weight.value) : null;
  11270. const Z = channels.z?.value;
  11271. const {z} = this;
  11272. const [cx, cy] = applyFrameAnchor(this, dimensions);
  11273. const {width, height} = dimensions;
  11274. // Get the (either scaled or projected) xy channels.
  11275. const {x: X, y: Y} = applyPosition(channels, scales, context);
  11276. // Group any of the input channels according to the first index associated
  11277. // with each z-series or facet. Drop any channels not be needed for
  11278. // rendering after the contours are computed.
  11279. const newChannels = Object.fromEntries(
  11280. Object.entries(channels)
  11281. .filter(([key]) => !dropChannels.has(key))
  11282. .map(([key, channel]) => [key, {...channel, value: []}])
  11283. );
  11284. // If the fill or stroke encodes density, construct new output channels.
  11285. const FD = fillDensity && [];
  11286. const SD = strokeDensity && [];
  11287. const density = d3.contourDensity()
  11288. .x(X ? (i) => X[i] : cx)
  11289. .y(Y ? (i) => Y[i] : cy)
  11290. .weight(W ? (i) => W[i] : 1)
  11291. .size([width, height])
  11292. .bandwidth(bandwidth);
  11293. // Compute the grid for each facet-series.
  11294. const facetsContours = [];
  11295. for (const facet of facets) {
  11296. const facetContours = [];
  11297. facetsContours.push(facetContours);
  11298. for (const index of Z ? groupZ(facet, Z, z) : [facet]) {
  11299. const contour = density.contours(index);
  11300. facetContours.push([index, contour]);
  11301. }
  11302. }
  11303. // If explicit thresholds were not specified, find the maximum density of
  11304. // all grids and use this to compute thresholds.
  11305. let T = thresholds;
  11306. if (!(T instanceof TypedArray)) {
  11307. let maxValue = 0;
  11308. for (const facetContours of facetsContours) {
  11309. for (const [, contour] of facetContours) {
  11310. const max = contour.max;
  11311. if (max > maxValue) maxValue = max;
  11312. }
  11313. }
  11314. T = Float64Array.from({length: thresholds - 1}, (_, i) => (maxValue * k * (i + 1)) / thresholds);
  11315. }
  11316. // Generate contours for each facet-series.
  11317. const newFacets = [];
  11318. const contours = [];
  11319. for (const facetContours of facetsContours) {
  11320. const newFacet = [];
  11321. newFacets.push(newFacet);
  11322. for (const [index, contour] of facetContours) {
  11323. for (const t of T) {
  11324. newFacet.push(contours.length);
  11325. contours.push(contour(t / k));
  11326. if (FD) FD.push(t);
  11327. if (SD) SD.push(t);
  11328. for (const key in newChannels) {
  11329. newChannels[key].value.push(channels[key].value[index[0]]);
  11330. }
  11331. }
  11332. }
  11333. }
  11334. // If the fill or stroke encodes density, ensure that a zero value is
  11335. // included so that the default color scale domain starts at zero. Otherwise
  11336. // if the starting range value is the same as the background color, the
  11337. // first contour might not be visible.
  11338. if (FD) FD.push(0);
  11339. if (SD) SD.push(0);
  11340. return {
  11341. data,
  11342. facets: newFacets,
  11343. channels: {
  11344. ...newChannels,
  11345. ...(FD && {fill: {value: FD, scale: "color"}}),
  11346. ...(SD && {stroke: {value: SD, scale: "color"}}),
  11347. contours: {value: contours}
  11348. }
  11349. };
  11350. });
  11351. }
  11352. function isDensity(value) {
  11353. return /^density$/i.test(value);
  11354. }
  11355. function differenceX(data, options) {
  11356. return differenceK("x", data, options);
  11357. }
  11358. function differenceY(data, options) {
  11359. return differenceK("y", data, options);
  11360. }
  11361. function differenceK(
  11362. k,
  11363. data,
  11364. {
  11365. x1,
  11366. x2,
  11367. y1,
  11368. y2,
  11369. x = x1 === undefined && x2 === undefined ? (k === "y" ? indexOf : identity$1) : undefined,
  11370. y = y1 === undefined && y2 === undefined ? (k === "x" ? indexOf : identity$1) : undefined,
  11371. fill, // ignored
  11372. positiveFill = "#3ca951",
  11373. negativeFill = "#4269d0",
  11374. fillOpacity = 1,
  11375. positiveFillOpacity = fillOpacity,
  11376. negativeFillOpacity = fillOpacity,
  11377. stroke,
  11378. strokeOpacity,
  11379. z = maybeColorChannel(stroke)[0],
  11380. clip, // optional additional clip for area
  11381. tip,
  11382. render,
  11383. ...options
  11384. } = {}
  11385. ) {
  11386. [x1, x2] = memoTuple(x, x1, x2);
  11387. [y1, y2] = memoTuple(y, y1, y2);
  11388. if (x1 === x2 && y1 === y2) {
  11389. if (k === "y") y1 = memo(0);
  11390. else x1 = memo(0);
  11391. }
  11392. ({tip} = withTip({tip}, k === "y" ? "x" : "y"));
  11393. return marks(
  11394. !isNoneish(positiveFill)
  11395. ? Object.assign(
  11396. area(data, {
  11397. x1,
  11398. x2,
  11399. y1,
  11400. y2,
  11401. z,
  11402. fill: positiveFill,
  11403. fillOpacity: positiveFillOpacity,
  11404. render: composeRender(render, clipDifference(k, true)),
  11405. clip,
  11406. ...options
  11407. }),
  11408. {ariaLabel: "positive difference"}
  11409. )
  11410. : null,
  11411. !isNoneish(negativeFill)
  11412. ? Object.assign(
  11413. area(data, {
  11414. x1,
  11415. x2,
  11416. y1,
  11417. y2,
  11418. z,
  11419. fill: negativeFill,
  11420. fillOpacity: negativeFillOpacity,
  11421. render: composeRender(render, clipDifference(k, false)),
  11422. clip,
  11423. ...options
  11424. }),
  11425. {ariaLabel: "negative difference"}
  11426. )
  11427. : null,
  11428. line(data, {
  11429. x: x2,
  11430. y: y2,
  11431. z,
  11432. stroke,
  11433. strokeOpacity,
  11434. tip,
  11435. clip: true,
  11436. ...options
  11437. })
  11438. );
  11439. }
  11440. function memoTuple(x, x1, x2) {
  11441. if (x1 === undefined && x2 === undefined) {
  11442. // {x} → [x, x]
  11443. x1 = x2 = memo(x);
  11444. } else if (x1 === undefined) {
  11445. // {x2} → [x2, x2]
  11446. // {x, x2} → [x, x2]
  11447. x2 = memo(x2);
  11448. x1 = x === undefined ? x2 : memo(x);
  11449. } else if (x2 === undefined) {
  11450. // {x1} → [x1, x1]
  11451. // {x, x1} → [x1, x]
  11452. x1 = memo(x1);
  11453. x2 = x === undefined ? x1 : memo(x);
  11454. } else {
  11455. // {x1, x2} → [x1, x2]
  11456. x1 = memo(x1);
  11457. x2 = memo(x2);
  11458. }
  11459. return [x1, x2];
  11460. }
  11461. function memo(v) {
  11462. let V;
  11463. const {value, label = labelof(value)} = maybeValue(v);
  11464. return {transform: (data) => V || (V = valueof(data, value)), label};
  11465. }
  11466. function clipDifference(k, positive) {
  11467. const f = k === "x" ? "y" : "x"; // f is the flipped dimension
  11468. const f1 = `${f}1`;
  11469. const f2 = `${f}2`;
  11470. const k1 = `${k}1`;
  11471. const k2 = `${k}2`;
  11472. return (index, scales, channels, dimensions, context, next) => {
  11473. const {[f1]: F1, [f2]: F2} = channels;
  11474. const K1 = new Float32Array(F1.length);
  11475. const K2 = new Float32Array(F2.length);
  11476. const m = dimensions[k === "y" ? "height" : "width"];
  11477. (positive === inferScaleOrder(scales[k]) < 0 ? K1 : K2).fill(m);
  11478. const oc = next(index, scales, {...channels, [f2]: F1, [k2]: K2}, dimensions, context);
  11479. const og = next(index, scales, {...channels, [f1]: F2, [k1]: K1}, dimensions, context);
  11480. const c = oc.querySelector("g") ?? oc; // applyClip
  11481. const g = og.querySelector("g") ?? og; // applyClip
  11482. for (let i = 0; c.firstChild; i += 2) {
  11483. const id = getClipId();
  11484. const clipPath = create("svg:clipPath", context).attr("id", id).node();
  11485. clipPath.appendChild(c.firstChild);
  11486. g.childNodes[i].setAttribute("clip-path", `url(#${id})`);
  11487. g.insertBefore(clipPath, g.childNodes[i]);
  11488. }
  11489. return og;
  11490. };
  11491. }
  11492. function centroid({geometry = identity$1, ...options} = {}) {
  11493. const getG = memoize1((data) => valueof(data, geometry));
  11494. return initializer(
  11495. // Suppress defaults for x and y since they will be computed by the initializer.
  11496. // Propagate the (memoized) geometry channel in case it’s still needed.
  11497. {...options, x: null, y: null, geometry: {transform: getG}},
  11498. (data, facets, channels, scales, dimensions, context) => {
  11499. const G = getG(data);
  11500. const n = G.length;
  11501. const X = new Float64Array(n);
  11502. const Y = new Float64Array(n);
  11503. const {centroid} = context.path();
  11504. for (let i = 0; i < n; ++i) [X[i], Y[i]] = centroid(G[i]);
  11505. return {
  11506. data,
  11507. facets,
  11508. channels: {x: {value: X, scale: null, source: null}, y: {value: Y, scale: null, source: null}}
  11509. };
  11510. }
  11511. );
  11512. }
  11513. function geoCentroid({geometry = identity$1, ...options} = {}) {
  11514. const getG = memoize1((data) => valueof(data, geometry));
  11515. const getC = memoize1((data) => valueof(getG(data), d3.geoCentroid));
  11516. return {
  11517. ...options,
  11518. x: {transform: (data) => Float64Array.from(getC(data), ([x]) => x)},
  11519. y: {transform: (data) => Float64Array.from(getC(data), ([, y]) => y)},
  11520. geometry: {transform: getG}
  11521. };
  11522. }
  11523. const defaults$3 = {
  11524. ariaLabel: "geo",
  11525. fill: "none",
  11526. stroke: "currentColor",
  11527. strokeWidth: 1,
  11528. strokeLinecap: "round",
  11529. strokeLinejoin: "round",
  11530. strokeMiterlimit: 1
  11531. };
  11532. class Geo extends Mark {
  11533. constructor(data, options = {}) {
  11534. const [vr, cr] = maybeNumberChannel(options.r, 3);
  11535. super(
  11536. data,
  11537. {
  11538. x: {value: options.tip ? options.x : null, scale: "x", optional: true},
  11539. y: {value: options.tip ? options.y : null, scale: "y", optional: true},
  11540. r: {value: vr, scale: "r", filter: positive, optional: true},
  11541. geometry: {value: options.geometry, scale: "projection"}
  11542. },
  11543. withDefaultSort(options),
  11544. defaults$3
  11545. );
  11546. this.r = cr;
  11547. }
  11548. render(index, scales, channels, dimensions, context) {
  11549. const {geometry: G, r: R} = channels;
  11550. const path = context.path();
  11551. const {r} = this;
  11552. if (negative(r)) index = [];
  11553. else if (r !== undefined) path.pointRadius(r);
  11554. return create("svg:g", context)
  11555. .call(applyIndirectStyles, this, dimensions, context)
  11556. .call(applyTransform, this, scales)
  11557. .call((g) => {
  11558. g.selectAll()
  11559. .data(index)
  11560. .enter()
  11561. .append("path")
  11562. .call(applyDirectStyles, this)
  11563. .attr("d", R ? (i) => path.pointRadius(R[i])(G[i]) : (i) => path(G[i]))
  11564. .call(applyChannelStyles, this, channels);
  11565. })
  11566. .node();
  11567. }
  11568. }
  11569. function geo(data, options = {}) {
  11570. if (options.tip && options.x === undefined && options.y === undefined) options = centroid(options);
  11571. else if (options.geometry === undefined) options = {...options, geometry: identity$1};
  11572. return new Geo(data, options);
  11573. }
  11574. function sphere({strokeWidth = 1.5, ...options} = {}) {
  11575. return geo({type: "Sphere"}, {strokeWidth, ...options});
  11576. }
  11577. function graticule({strokeOpacity = 0.1, ...options} = {}) {
  11578. return geo(d3.geoGraticule10(), {strokeOpacity, ...options});
  11579. }
  11580. // We don’t want the hexagons to align with the edges of the plot frame, as that
  11581. // would cause extreme x-values (the upper bound of the default x-scale domain)
  11582. // to be rounded up into a floating bin to the right of the plot. Therefore,
  11583. // rather than centering the origin hexagon around ⟨0,0⟩ in screen coordinates,
  11584. // we offset slightly to ⟨0.5,0⟩. The hexgrid mark uses the same origin.
  11585. const ox = 0.5,
  11586. oy = 0;
  11587. function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
  11588. const {z} = options;
  11589. // TODO filter e.g. to show empty hexbins?
  11590. binWidth = binWidth === undefined ? 20 : number$1(binWidth);
  11591. outputs = maybeGroupOutputs(outputs, options);
  11592. // A fill output means a fill channel; declaring the channel here instead of
  11593. // waiting for the initializer allows the mark constructor to determine that
  11594. // the stroke should default to none (assuming a mark that defaults to fill
  11595. // and no stroke, such as dot). Note that it’s safe to mutate options here
  11596. // because we just created it with the rest operator above.
  11597. if (hasOutput(outputs, "fill")) options.channels = {...options.channels, fill: {value: []}};
  11598. // Populate default values for the r and symbol options, as appropriate.
  11599. if (options.symbol === undefined) options.symbol = "hexagon";
  11600. if (options.r === undefined && !hasOutput(outputs, "r")) options.r = binWidth / 2;
  11601. return initializer(options, (data, facets, channels, scales, _, context) => {
  11602. let {x: X, y: Y, z: Z, fill: F, stroke: S, symbol: Q} = channels;
  11603. if (X === undefined) throw new Error("missing channel: x");
  11604. if (Y === undefined) throw new Error("missing channel: y");
  11605. // Get the (either scaled or projected) xy channels.
  11606. ({x: X, y: Y} = applyPosition(channels, scales, context));
  11607. // Extract the values for channels that are eligible for grouping; not all
  11608. // marks define a z channel, so compute one if it not already computed. If z
  11609. // was explicitly set to null, ensure that we don’t subdivide bins.
  11610. Z = Z ? Z.value : valueof(data, z);
  11611. F = F?.value;
  11612. S = S?.value;
  11613. Q = Q?.value;
  11614. // Group on the first of z, fill, stroke, and symbol. Implicitly reduce
  11615. // these channels using the first corresponding value for each bin.
  11616. const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S, symbol: Q});
  11617. const GZ = Z && [];
  11618. const GF = F && [];
  11619. const GS = S && [];
  11620. const GQ = Q && [];
  11621. // Construct the hexbins and populate the output channels.
  11622. const binFacets = [];
  11623. const BX = [];
  11624. const BY = [];
  11625. let i = -1;
  11626. for (const o of outputs) o.initialize(data);
  11627. for (const facet of facets) {
  11628. const binFacet = [];
  11629. for (const o of outputs) o.scope("facet", facet);
  11630. for (const [f, I] of maybeGroup(facet, G)) {
  11631. for (const {index: b, extent} of hbin(data, I, X, Y, binWidth)) {
  11632. binFacet.push(++i);
  11633. BX.push(extent.x);
  11634. BY.push(extent.y);
  11635. if (Z) GZ.push(G === Z ? f : Z[b[0]]);
  11636. if (F) GF.push(G === F ? f : F[b[0]]);
  11637. if (S) GS.push(G === S ? f : S[b[0]]);
  11638. if (Q) GQ.push(G === Q ? f : Q[b[0]]);
  11639. for (const o of outputs) o.reduce(b, extent);
  11640. }
  11641. }
  11642. binFacets.push(binFacet);
  11643. }
  11644. // Construct the output channels, and populate the radius scale hint.
  11645. const sx = channels.x.scale;
  11646. const sy = channels.y.scale;
  11647. const binChannels = {
  11648. x: {value: BX, source: scales[sx] ? {value: map$1(BX, scales[sx].invert), scale: sx} : null},
  11649. y: {value: BY, source: scales[sy] ? {value: map$1(BY, scales[sy].invert), scale: sy} : null},
  11650. ...(Z && {z: {value: GZ}}),
  11651. ...(F && {fill: {value: GF, scale: "auto"}}),
  11652. ...(S && {stroke: {value: GS, scale: "auto"}}),
  11653. ...(Q && {symbol: {value: GQ, scale: "auto"}}),
  11654. ...Object.fromEntries(
  11655. outputs.map(({name, output}) => [
  11656. name,
  11657. {
  11658. scale: "auto",
  11659. label: output.label,
  11660. radius: name === "r" ? binWidth / 2 : undefined,
  11661. value: output.transform()
  11662. }
  11663. ])
  11664. )
  11665. };
  11666. return {data, facets: binFacets, channels: binChannels};
  11667. });
  11668. }
  11669. function hbin(data, I, X, Y, dx) {
  11670. const dy = dx * (1.5 / sqrt3);
  11671. const bins = new Map();
  11672. for (const i of I) {
  11673. let px = X[i],
  11674. py = Y[i];
  11675. if (isNaN(px) || isNaN(py)) continue;
  11676. let pj = Math.round((py = (py - oy) / dy)),
  11677. pi = Math.round((px = (px - ox) / dx - (pj & 1) / 2)),
  11678. py1 = py - pj;
  11679. if (Math.abs(py1) * 3 > 1) {
  11680. let px1 = px - pi,
  11681. pi2 = pi + (px < pi ? -1 : 1) / 2,
  11682. pj2 = pj + (py < pj ? -1 : 1),
  11683. px2 = px - pi2,
  11684. py2 = py - pj2;
  11685. if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) (pi = pi2 + (pj & 1 ? 1 : -1) / 2), (pj = pj2);
  11686. }
  11687. const key = `${pi},${pj}`;
  11688. let bin = bins.get(key);
  11689. if (bin === undefined) {
  11690. bin = {index: [], extent: {data, x: (pi + (pj & 1) / 2) * dx + ox, y: pj * dy + oy}};
  11691. bins.set(key, bin);
  11692. }
  11693. bin.index.push(i);
  11694. }
  11695. return bins.values();
  11696. }
  11697. const defaults$2 = {
  11698. ariaLabel: "hexgrid",
  11699. fill: "none",
  11700. stroke: "currentColor",
  11701. strokeOpacity: 0.1
  11702. };
  11703. function hexgrid(options) {
  11704. return new Hexgrid(options);
  11705. }
  11706. class Hexgrid extends Mark {
  11707. constructor({binWidth = 20, clip = true, ...options} = {}) {
  11708. super(singleton, undefined, {clip, ...options}, defaults$2);
  11709. this.binWidth = number$1(binWidth);
  11710. }
  11711. render(index, scales, channels, dimensions, context) {
  11712. const {binWidth} = this;
  11713. const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
  11714. const x0 = marginLeft - ox,
  11715. x1 = width - marginRight - ox,
  11716. y0 = marginTop - oy,
  11717. y1 = height - marginBottom - oy,
  11718. rx = binWidth / 2,
  11719. ry = rx * sqrt4_3,
  11720. hy = ry / 2,
  11721. wx = rx * 2,
  11722. wy = ry * 1.5,
  11723. i0 = Math.floor(x0 / wx),
  11724. i1 = Math.ceil(x1 / wx),
  11725. j0 = Math.floor((y0 + hy) / wy),
  11726. j1 = Math.ceil((y1 - hy) / wy) + 1,
  11727. path = `m0,${round(-ry)}l${round(rx)},${round(hy)}v${round(ry)}l${round(-rx)},${round(hy)}`;
  11728. let d = path;
  11729. for (let j = j0; j < j1; ++j) {
  11730. for (let i = i0; i < i1; ++i) {
  11731. d += `M${round(i * wx + (j & 1) * rx)},${round(j * wy)}${path}`;
  11732. }
  11733. }
  11734. return create("svg:g", context)
  11735. .datum(0)
  11736. .call(applyIndirectStyles, this, dimensions, context)
  11737. .call(applyTransform, this, {}, offset + ox, offset + oy)
  11738. .call((g) => g.append("path").call(applyDirectStyles, this).call(applyChannelStyles, this, channels).attr("d", d))
  11739. .node();
  11740. }
  11741. }
  11742. function round(x) {
  11743. return Math.round(x * 1e3) / 1e3;
  11744. }
  11745. const defaults$1 = {
  11746. ariaLabel: "image",
  11747. fill: null,
  11748. stroke: null
  11749. };
  11750. // Tests if the given string is a path: does it start with a dot-slash
  11751. // (./foo.png), dot-dot-slash (../foo.png), or slash (/foo.png)?
  11752. function isPath(string) {
  11753. return /^\.*\//.test(string);
  11754. }
  11755. // Tests if the given string is a URL (e.g., https://placekitten.com/200/300).
  11756. // The allowed protocols is overly restrictive, but we don’t want to allow any
  11757. // scheme here because it would increase the likelihood of a false positive with
  11758. // a field name that happens to contain a colon.
  11759. function isUrl(string) {
  11760. return /^(blob|data|file|http|https):/i.test(string);
  11761. }
  11762. // Disambiguates a constant src definition from a channel. A path or URL string
  11763. // is assumed to be a constant; any other string is assumed to be a field name.
  11764. function maybePathChannel(value) {
  11765. return typeof value === "string" && (isPath(value) || isUrl(value)) ? [undefined, value] : [value, undefined];
  11766. }
  11767. class Image extends Mark {
  11768. constructor(data, options = {}) {
  11769. let {x, y, r, width, height, rotate, src, preserveAspectRatio, crossOrigin, frameAnchor, imageRendering} = options;
  11770. if (r == null) r = undefined;
  11771. if (r === undefined && width === undefined && height === undefined) width = height = 16;
  11772. else if (width === undefined && height !== undefined) width = height;
  11773. else if (height === undefined && width !== undefined) height = width;
  11774. const [vs, cs] = maybePathChannel(src);
  11775. const [vr, cr] = maybeNumberChannel(r);
  11776. const [vw, cw] = maybeNumberChannel(width, cr !== undefined ? cr * 2 : undefined);
  11777. const [vh, ch] = maybeNumberChannel(height, cr !== undefined ? cr * 2 : undefined);
  11778. const [va, ca] = maybeNumberChannel(rotate, 0);
  11779. super(
  11780. data,
  11781. {
  11782. x: {value: x, scale: "x", optional: true},
  11783. y: {value: y, scale: "y", optional: true},
  11784. r: {value: vr, scale: "r", filter: positive, optional: true},
  11785. width: {value: vw, filter: positive, optional: true},
  11786. height: {value: vh, filter: positive, optional: true},
  11787. rotate: {value: va, optional: true},
  11788. src: {value: vs, optional: true}
  11789. },
  11790. withDefaultSort(options),
  11791. defaults$1
  11792. );
  11793. this.src = cs;
  11794. this.width = cw;
  11795. this.rotate = ca;
  11796. this.height = ch;
  11797. this.r = cr;
  11798. this.preserveAspectRatio = impliedString(preserveAspectRatio, "xMidYMid");
  11799. this.crossOrigin = string(crossOrigin);
  11800. this.frameAnchor = maybeFrameAnchor(frameAnchor);
  11801. this.imageRendering = impliedString(imageRendering, "auto");
  11802. }
  11803. render(index, scales, channels, dimensions, context) {
  11804. const {x, y} = scales;
  11805. const {x: X, y: Y, width: W, height: H, r: R, rotate: A, src: S} = channels;
  11806. const {r, width, height, rotate} = this;
  11807. const [cx, cy] = applyFrameAnchor(this, dimensions);
  11808. return create("svg:g", context)
  11809. .call(applyIndirectStyles, this, dimensions, context)
  11810. .call(applyTransform, this, {x: X && x, y: Y && y})
  11811. .call((g) =>
  11812. g
  11813. .selectAll()
  11814. .data(index)
  11815. .enter()
  11816. .append("image")
  11817. .call(applyDirectStyles, this)
  11818. .attr("x", position(X, W, R, cx, width, r))
  11819. .attr("y", position(Y, H, R, cy, height, r))
  11820. .attr("width", W ? (i) => W[i] : width !== undefined ? width : R ? (i) => R[i] * 2 : r * 2)
  11821. .attr("height", H ? (i) => H[i] : height !== undefined ? height : R ? (i) => R[i] * 2 : r * 2)
  11822. // TODO: combine x, y, rotate and transform-origin into a single transform
  11823. .attr("transform", A ? (i) => `rotate(${A[i]})` : rotate ? `rotate(${rotate})` : null)
  11824. .attr("transform-origin", A || rotate ? template`${X ? (i) => X[i] : cx}px ${Y ? (i) => Y[i] : cy}px` : null)
  11825. .call(applyAttr, "href", S ? (i) => S[i] : this.src)
  11826. .call(applyAttr, "preserveAspectRatio", this.preserveAspectRatio)
  11827. .call(applyAttr, "crossorigin", this.crossOrigin)
  11828. .call(applyAttr, "image-rendering", this.imageRendering)
  11829. .call(applyAttr, "clip-path", R ? (i) => `circle(${R[i]}px)` : r !== undefined ? `circle(${r}px)` : null)
  11830. .call(applyChannelStyles, this, channels)
  11831. )
  11832. .node();
  11833. }
  11834. }
  11835. function position(X, W, R, x, w, r) {
  11836. return W && X
  11837. ? (i) => X[i] - W[i] / 2
  11838. : W
  11839. ? (i) => x - W[i] / 2
  11840. : X && w !== undefined
  11841. ? (i) => X[i] - w / 2
  11842. : w !== undefined
  11843. ? x - w / 2
  11844. : R && X
  11845. ? (i) => X[i] - R[i]
  11846. : R
  11847. ? (i) => x - R[i]
  11848. : X
  11849. ? (i) => X[i] - r
  11850. : x - r;
  11851. }
  11852. function image(data, {x, y, ...options} = {}) {
  11853. if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y);
  11854. return new Image(data, {...options, x, y});
  11855. }
  11856. // https://github.com/jstat/jstat
  11857. //
  11858. // Copyright (c) 2013 jStat
  11859. //
  11860. // Permission is hereby granted, free of charge, to any person obtaining a copy
  11861. // of this software and associated documentation files (the "Software"), to deal
  11862. // in the Software without restriction, including without limitation the rights
  11863. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11864. // copies of the Software, and to permit persons to whom the Software is
  11865. // furnished to do so, subject to the following conditions:
  11866. //
  11867. // The above copyright notice and this permission notice shall be included in
  11868. // all copies or substantial portions of the Software.
  11869. //
  11870. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  11871. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  11872. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  11873. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  11874. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  11875. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  11876. // SOFTWARE.
  11877. function ibetainv(p, a, b) {
  11878. var EPS = 1e-8;
  11879. var a1 = a - 1;
  11880. var b1 = b - 1;
  11881. var j = 0;
  11882. var lna, lnb, t, u, err, x, w, afac;
  11883. if (p <= 0) return 0;
  11884. if (p >= 1) return 1;
  11885. {
  11886. lna = Math.log(a / (a + b));
  11887. lnb = Math.log(b / (a + b));
  11888. t = Math.exp(a * lna) / a;
  11889. u = Math.exp(b * lnb) / b;
  11890. w = t + u;
  11891. if (p < t / w) x = Math.pow(a * w * p, 1 / a);
  11892. else x = 1 - Math.pow(b * w * (1 - p), 1 / b);
  11893. }
  11894. afac = -gammaln(a) - gammaln(b) + gammaln(a + b);
  11895. for (; j < 10; j++) {
  11896. if (x === 0 || x === 1) return x;
  11897. err = ibeta(x, a, b) - p;
  11898. t = Math.exp(a1 * Math.log(x) + b1 * Math.log(1 - x) + afac);
  11899. u = err / t;
  11900. x -= t = u / (1 - 0.5 * Math.min(1, u * (a1 / x - b1 / (1 - x))));
  11901. if (x <= 0) x = 0.5 * (x + t);
  11902. if (x >= 1) x = 0.5 * (x + t + 1);
  11903. if (Math.abs(t) < EPS * x && j > 0) break;
  11904. }
  11905. return x;
  11906. }
  11907. function ibeta(x, a, b) {
  11908. // Factors in front of the continued fraction.
  11909. var bt =
  11910. x === 0 || x === 1 ? 0 : Math.exp(gammaln(a + b) - gammaln(a) - gammaln(b) + a * Math.log(x) + b * Math.log(1 - x));
  11911. if (x < 0 || x > 1) return false;
  11912. if (x < (a + 1) / (a + b + 2))
  11913. // Use continued fraction directly.
  11914. return (bt * betacf(x, a, b)) / a;
  11915. // else use continued fraction after making the symmetry transformation.
  11916. return 1 - (bt * betacf(1 - x, b, a)) / b;
  11917. }
  11918. function betacf(x, a, b) {
  11919. var fpmin = 1e-30;
  11920. var m = 1;
  11921. var qab = a + b;
  11922. var qap = a + 1;
  11923. var qam = a - 1;
  11924. var c = 1;
  11925. var d = 1 - (qab * x) / qap;
  11926. var m2, aa, del, h;
  11927. // These q's will be used in factors that occur in the coefficients
  11928. if (Math.abs(d) < fpmin) d = fpmin;
  11929. d = 1 / d;
  11930. h = d;
  11931. for (; m <= 100; m++) {
  11932. m2 = 2 * m;
  11933. aa = (m * (b - m) * x) / ((qam + m2) * (a + m2));
  11934. // One step (the even one) of the recurrence
  11935. d = 1 + aa * d;
  11936. if (Math.abs(d) < fpmin) d = fpmin;
  11937. c = 1 + aa / c;
  11938. if (Math.abs(c) < fpmin) c = fpmin;
  11939. d = 1 / d;
  11940. h *= d * c;
  11941. aa = (-(a + m) * (qab + m) * x) / ((a + m2) * (qap + m2));
  11942. // Next step of the recurrence (the odd one)
  11943. d = 1 + aa * d;
  11944. if (Math.abs(d) < fpmin) d = fpmin;
  11945. c = 1 + aa / c;
  11946. if (Math.abs(c) < fpmin) c = fpmin;
  11947. d = 1 / d;
  11948. del = d * c;
  11949. h *= del;
  11950. if (Math.abs(del - 1.0) < 3e-7) break;
  11951. }
  11952. return h;
  11953. }
  11954. function gammaln(x) {
  11955. var j = 0;
  11956. var cof = [
  11957. 76.18009172947146, -86.5053203294167, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2,
  11958. -5395239384953e-18
  11959. ];
  11960. var ser = 1.000000000190015;
  11961. var xx, y, tmp;
  11962. tmp = (y = xx = x) + 5.5;
  11963. tmp -= (xx + 0.5) * Math.log(tmp);
  11964. for (; j < 6; j++) ser += cof[j] / ++y;
  11965. return Math.log((2.506628274631 * ser) / xx) - tmp;
  11966. }
  11967. function qt(p, dof) {
  11968. var x = ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5);
  11969. x = Math.sqrt((dof * (1 - x)) / x);
  11970. return p > 0.5 ? x : -x;
  11971. }
  11972. const defaults = {
  11973. ariaLabel: "linear-regression",
  11974. fill: "currentColor",
  11975. fillOpacity: 0.1,
  11976. stroke: "currentColor",
  11977. strokeWidth: 1.5,
  11978. strokeLinecap: "round",
  11979. strokeLinejoin: "round",
  11980. strokeMiterlimit: 1
  11981. };
  11982. class LinearRegression extends Mark {
  11983. constructor(data, options = {}) {
  11984. const {x, y, z, ci = 0.95, precision = 4} = options;
  11985. super(
  11986. data,
  11987. {
  11988. x: {value: x, scale: "x"},
  11989. y: {value: y, scale: "y"},
  11990. z: {value: maybeZ(options), optional: true}
  11991. },
  11992. options,
  11993. defaults
  11994. );
  11995. this.z = z;
  11996. this.ci = +ci;
  11997. this.precision = +precision;
  11998. if (!(0 <= this.ci && this.ci < 1)) throw new Error(`invalid ci; not in [0, 1): ${ci}`);
  11999. if (!(this.precision > 0)) throw new Error(`invalid precision: ${precision}`);
  12000. }
  12001. render(index, scales, channels, dimensions, context) {
  12002. const {x: X, y: Y, z: Z} = channels;
  12003. const {ci} = this;
  12004. return create("svg:g", context)
  12005. .call(applyIndirectStyles, this, dimensions, context)
  12006. .call(applyTransform, this, scales)
  12007. .call((g) =>
  12008. g
  12009. .selectAll()
  12010. .data(Z ? groupZ(index, Z, this.z) : [index])
  12011. .enter()
  12012. .call((enter) =>
  12013. enter
  12014. .append("path")
  12015. .attr("fill", "none")
  12016. .call(applyDirectStyles, this)
  12017. .call(applyGroupedChannelStyles, this, {...channels, fill: null, fillOpacity: null})
  12018. .attr("d", (I) => this._renderLine(I, X, Y))
  12019. .call(
  12020. ci && !isNone(this.fill)
  12021. ? (path) =>
  12022. path
  12023. .select(pathBefore)
  12024. .attr("stroke", "none")
  12025. .call(applyDirectStyles, this)
  12026. .call(applyGroupedChannelStyles, this, {
  12027. ...channels,
  12028. stroke: null,
  12029. strokeOpacity: null,
  12030. strokeWidth: null
  12031. })
  12032. .attr("d", (I) => this._renderBand(I, X, Y))
  12033. : () => {}
  12034. )
  12035. )
  12036. )
  12037. .node();
  12038. }
  12039. }
  12040. function pathBefore() {
  12041. return this.parentNode.insertBefore(this.ownerDocument.createElementNS(d3.namespaces.svg, "path"), this);
  12042. }
  12043. class LinearRegressionX extends LinearRegression {
  12044. constructor(data, options) {
  12045. super(data, options);
  12046. }
  12047. _renderBand(I, X, Y) {
  12048. const {ci, precision} = this;
  12049. const [y1, y2] = d3.extent(I, (i) => Y[i]);
  12050. const f = linearRegressionF(I, Y, X);
  12051. const g = confidenceIntervalF(I, Y, X, (1 - ci) / 2, f);
  12052. return d3.area()
  12053. .y((y) => y)
  12054. .x0((y) => g(y, -1))
  12055. .x1((y) => g(y, 1))(d3.range(y1, y2 - precision / 2, precision).concat(y2));
  12056. }
  12057. _renderLine(I, X, Y) {
  12058. const [y1, y2] = d3.extent(I, (i) => Y[i]);
  12059. const f = linearRegressionF(I, Y, X);
  12060. return `M${f(y1)},${y1}L${f(y2)},${y2}`;
  12061. }
  12062. }
  12063. class LinearRegressionY extends LinearRegression {
  12064. constructor(data, options) {
  12065. super(data, options);
  12066. }
  12067. _renderBand(I, X, Y) {
  12068. const {ci, precision} = this;
  12069. const [x1, x2] = d3.extent(I, (i) => X[i]);
  12070. const f = linearRegressionF(I, X, Y);
  12071. const g = confidenceIntervalF(I, X, Y, (1 - ci) / 2, f);
  12072. return d3.area()
  12073. .x((x) => x)
  12074. .y0((x) => g(x, -1))
  12075. .y1((x) => g(x, 1))(d3.range(x1, x2 - precision / 2, precision).concat(x2));
  12076. }
  12077. _renderLine(I, X, Y) {
  12078. const [x1, x2] = d3.extent(I, (i) => X[i]);
  12079. const f = linearRegressionF(I, X, Y);
  12080. return `M${x1},${f(x1)}L${x2},${f(x2)}`;
  12081. }
  12082. }
  12083. function linearRegressionX(
  12084. data,
  12085. {y = indexOf, x = identity$1, stroke, fill = isNoneish(stroke) ? "currentColor" : stroke, ...options} = {}
  12086. ) {
  12087. return new LinearRegressionX(data, maybeDenseIntervalY({...options, x, y, fill, stroke}));
  12088. }
  12089. function linearRegressionY(
  12090. data,
  12091. {x = indexOf, y = identity$1, stroke, fill = isNoneish(stroke) ? "currentColor" : stroke, ...options} = {}
  12092. ) {
  12093. return new LinearRegressionY(data, maybeDenseIntervalX({...options, x, y, fill, stroke}));
  12094. }
  12095. function linearRegressionF(I, X, Y) {
  12096. let sumX = 0,
  12097. sumY = 0,
  12098. sumXY = 0,
  12099. sumX2 = 0;
  12100. for (const i of I) {
  12101. const xi = X[i];
  12102. const yi = Y[i];
  12103. sumX += xi;
  12104. sumY += yi;
  12105. sumXY += xi * yi;
  12106. sumX2 += xi * xi;
  12107. }
  12108. const n = I.length;
  12109. const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
  12110. const intercept = (sumY - slope * sumX) / n;
  12111. return (x) => slope * x + intercept;
  12112. }
  12113. function confidenceIntervalF(I, X, Y, p, f) {
  12114. const mean = d3.sum(I, (i) => X[i]) / I.length;
  12115. let a = 0,
  12116. b = 0;
  12117. for (const i of I) {
  12118. a += (X[i] - mean) ** 2;
  12119. b += (Y[i] - f(X[i])) ** 2;
  12120. }
  12121. const sy = Math.sqrt(b / (I.length - 2));
  12122. const t = qt(p, I.length - 2);
  12123. return (x, k) => {
  12124. const Y = f(x);
  12125. const se = sy * Math.sqrt(1 / I.length + (x - mean) ** 2 / a);
  12126. return Y + k * t * se;
  12127. };
  12128. }
  12129. function treeNode({
  12130. path = identity$1, // the delimited path
  12131. delimiter, // how the path is separated
  12132. frameAnchor,
  12133. treeLayout = d3.tree,
  12134. treeSort,
  12135. treeSeparation,
  12136. treeAnchor,
  12137. treeFilter,
  12138. ...options
  12139. } = {}) {
  12140. treeAnchor = maybeTreeAnchor(treeAnchor);
  12141. treeSort = maybeTreeSort(treeSort);
  12142. if (treeFilter != null) treeFilter = maybeNodeValue(treeFilter);
  12143. if (frameAnchor === undefined) frameAnchor = treeAnchor.frameAnchor;
  12144. const normalize = normalizer(delimiter);
  12145. const outputs = treeOutputs(options, maybeNodeValue);
  12146. const [X, setX] = column();
  12147. const [Y, setY] = column();
  12148. return {
  12149. x: X,
  12150. y: Y,
  12151. frameAnchor,
  12152. ...basic(options, (data, facets) => {
  12153. const P = normalize(valueof(data, path));
  12154. const X = setX([]);
  12155. const Y = setY([]);
  12156. let treeIndex = -1;
  12157. const treeData = [];
  12158. const treeFacets = [];
  12159. const rootof = d3.stratify().path((i) => P[i]);
  12160. const setData = isArray(data)
  12161. ? (node) => (node.data = data[node.data])
  12162. : (node) => (node.data = data.get(node.data));
  12163. const layout = treeLayout();
  12164. if (layout.nodeSize) layout.nodeSize([1, 1]);
  12165. if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one);
  12166. for (const o of outputs) o[output_values] = o[output_setValues]([]);
  12167. for (const facet of facets) {
  12168. const treeFacet = [];
  12169. const root = rootof(facet.filter((i) => P[i] != null)).each(setData);
  12170. if (treeSort != null) root.sort(treeSort);
  12171. layout(root);
  12172. for (const node of root.descendants()) {
  12173. if (treeFilter != null && !treeFilter(node)) continue;
  12174. treeFacet.push(++treeIndex);
  12175. treeData[treeIndex] = node.data;
  12176. treeAnchor.position(node, treeIndex, X, Y);
  12177. for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node);
  12178. }
  12179. treeFacets.push(treeFacet);
  12180. }
  12181. return {data: treeData, facets: treeFacets};
  12182. }),
  12183. ...Object.fromEntries(outputs)
  12184. };
  12185. }
  12186. function treeLink({
  12187. path = identity$1, // the delimited path
  12188. delimiter, // how the path is separated
  12189. curve = "bump-x",
  12190. stroke = "#555",
  12191. strokeWidth = 1.5,
  12192. strokeOpacity = 0.5,
  12193. treeLayout = d3.tree,
  12194. treeSort,
  12195. treeSeparation,
  12196. treeAnchor,
  12197. treeFilter,
  12198. ...options
  12199. } = {}) {
  12200. treeAnchor = maybeTreeAnchor(treeAnchor);
  12201. treeSort = maybeTreeSort(treeSort);
  12202. if (treeFilter != null) treeFilter = maybeLinkValue(treeFilter);
  12203. options = {curve, stroke, strokeWidth, strokeOpacity, ...options};
  12204. const normalize = normalizer(delimiter);
  12205. const outputs = treeOutputs(options, maybeLinkValue);
  12206. const [X1, setX1] = column();
  12207. const [X2, setX2] = column();
  12208. const [Y1, setY1] = column();
  12209. const [Y2, setY2] = column();
  12210. return {
  12211. x1: X1,
  12212. x2: X2,
  12213. y1: Y1,
  12214. y2: Y2,
  12215. ...basic(options, (data, facets) => {
  12216. const P = normalize(valueof(data, path));
  12217. const X1 = setX1([]);
  12218. const X2 = setX2([]);
  12219. const Y1 = setY1([]);
  12220. const Y2 = setY2([]);
  12221. let treeIndex = -1;
  12222. const treeData = [];
  12223. const treeFacets = [];
  12224. const rootof = d3.stratify().path((i) => P[i]);
  12225. const layout = treeLayout();
  12226. if (layout.nodeSize) layout.nodeSize([1, 1]);
  12227. if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one);
  12228. for (const o of outputs) o[output_values] = o[output_setValues]([]);
  12229. for (const facet of facets) {
  12230. const treeFacet = [];
  12231. const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data]));
  12232. if (treeSort != null) root.sort(treeSort);
  12233. layout(root);
  12234. for (const {source, target} of root.links()) {
  12235. if (treeFilter != null && !treeFilter(target, source)) continue;
  12236. treeFacet.push(++treeIndex);
  12237. treeData[treeIndex] = target.data;
  12238. treeAnchor.position(source, treeIndex, X1, Y1);
  12239. treeAnchor.position(target, treeIndex, X2, Y2);
  12240. for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](target, source);
  12241. }
  12242. treeFacets.push(treeFacet);
  12243. }
  12244. return {data: treeData, facets: treeFacets};
  12245. }),
  12246. ...Object.fromEntries(outputs)
  12247. };
  12248. }
  12249. function maybeTreeAnchor(anchor = "left") {
  12250. switch (`${anchor}`.trim().toLowerCase()) {
  12251. case "left":
  12252. return treeAnchorLeft;
  12253. case "right":
  12254. return treeAnchorRight;
  12255. }
  12256. throw new Error(`invalid tree anchor: ${anchor}`);
  12257. }
  12258. const treeAnchorLeft = {
  12259. frameAnchor: "left",
  12260. dx: 6,
  12261. position({x, y}, i, X, Y) {
  12262. X[i] = y;
  12263. Y[i] = -x;
  12264. }
  12265. };
  12266. const treeAnchorRight = {
  12267. frameAnchor: "right",
  12268. dx: -6,
  12269. position({x, y}, i, X, Y) {
  12270. X[i] = -y;
  12271. Y[i] = -x;
  12272. }
  12273. };
  12274. function maybeTreeSort(sort) {
  12275. return sort == null || typeof sort === "function"
  12276. ? sort
  12277. : `${sort}`.trim().toLowerCase().startsWith("node:")
  12278. ? nodeSort(maybeNodeValue(sort))
  12279. : nodeSort(nodeData(sort));
  12280. }
  12281. function nodeSort(value) {
  12282. return (a, b) => ascendingDefined(value(a), value(b));
  12283. }
  12284. function nodeData(field) {
  12285. return (node) => node.data?.[field];
  12286. }
  12287. function normalizer(delimiter = "/") {
  12288. delimiter = `${delimiter}`;
  12289. if (delimiter === "/") return (P) => P; // paths are already slash-separated
  12290. if (delimiter.length !== 1) throw new Error("delimiter must be exactly one character");
  12291. const delimiterCode = delimiter.charCodeAt(0);
  12292. return (P) => P.map((p) => slashDelimiter(p, delimiterCode));
  12293. }
  12294. const CODE_BACKSLASH = 92;
  12295. const CODE_SLASH = 47;
  12296. function slashDelimiter(input, delimiterCode) {
  12297. if (delimiterCode === CODE_BACKSLASH) throw new Error("delimiter cannot be backslash");
  12298. let afterBackslash = false;
  12299. for (let i = 0, n = input.length; i < n; ++i) {
  12300. switch (input.charCodeAt(i)) {
  12301. case CODE_BACKSLASH:
  12302. if (!afterBackslash) {
  12303. afterBackslash = true;
  12304. continue;
  12305. }
  12306. break;
  12307. case delimiterCode:
  12308. if (afterBackslash) {
  12309. (input = input.slice(0, i - 1) + input.slice(i)), --i, --n; // remove backslash
  12310. } else {
  12311. input = input.slice(0, i) + "/" + input.slice(i + 1); // replace delimiter with slash
  12312. }
  12313. break;
  12314. case CODE_SLASH:
  12315. if (afterBackslash) {
  12316. (input = input.slice(0, i) + "\\\\" + input.slice(i)), (i += 2), (n += 2); // add two backslashes
  12317. } else {
  12318. (input = input.slice(0, i) + "\\" + input.slice(i)), ++i, ++n; // add backslash
  12319. }
  12320. break;
  12321. }
  12322. afterBackslash = false;
  12323. }
  12324. return input;
  12325. }
  12326. function slashUnescape(input) {
  12327. let afterBackslash = false;
  12328. for (let i = 0, n = input.length; i < n; ++i) {
  12329. switch (input.charCodeAt(i)) {
  12330. case CODE_BACKSLASH:
  12331. if (!afterBackslash) {
  12332. afterBackslash = true;
  12333. continue;
  12334. }
  12335. // eslint-disable-next-line no-fallthrough
  12336. case CODE_SLASH:
  12337. if (afterBackslash) {
  12338. (input = input.slice(0, i - 1) + input.slice(i)), --i, --n; // remove backslash
  12339. }
  12340. break;
  12341. }
  12342. afterBackslash = false;
  12343. }
  12344. return input;
  12345. }
  12346. function isNodeValue(option) {
  12347. return isObject(option) && typeof option.node === "function";
  12348. }
  12349. function isLinkValue(option) {
  12350. return isObject(option) && typeof option.link === "function";
  12351. }
  12352. function maybeNodeValue(value) {
  12353. if (isNodeValue(value)) return value.node;
  12354. value = `${value}`.trim().toLowerCase();
  12355. if (!value.startsWith("node:")) return;
  12356. switch (value) {
  12357. case "node:name":
  12358. return nodeName;
  12359. case "node:path":
  12360. return nodePath;
  12361. case "node:internal":
  12362. return nodeInternal;
  12363. case "node:external":
  12364. return nodeExternal;
  12365. case "node:depth":
  12366. return nodeDepth;
  12367. case "node:height":
  12368. return nodeHeight;
  12369. }
  12370. throw new Error(`invalid node value: ${value}`);
  12371. }
  12372. function maybeLinkValue(value) {
  12373. if (isNodeValue(value)) return value.node;
  12374. if (isLinkValue(value)) return value.link;
  12375. value = `${value}`.trim().toLowerCase();
  12376. if (!value.startsWith("node:") && !value.startsWith("parent:")) return;
  12377. switch (value) {
  12378. case "parent:name":
  12379. return parentValue(nodeName);
  12380. case "parent:path":
  12381. return parentValue(nodePath);
  12382. case "parent:depth":
  12383. return parentValue(nodeDepth);
  12384. case "parent:height":
  12385. return parentValue(nodeHeight);
  12386. case "node:name":
  12387. return nodeName;
  12388. case "node:path":
  12389. return nodePath;
  12390. case "node:internal":
  12391. return nodeInternal;
  12392. case "node:external":
  12393. return nodeExternal;
  12394. case "node:depth":
  12395. return nodeDepth;
  12396. case "node:height":
  12397. return nodeHeight;
  12398. }
  12399. throw new Error(`invalid link value: ${value}`);
  12400. }
  12401. function nodePath(node) {
  12402. return node.id;
  12403. }
  12404. function nodeName(node) {
  12405. return nameof(node.id);
  12406. }
  12407. function nodeDepth(node) {
  12408. return node.depth;
  12409. }
  12410. function nodeHeight(node) {
  12411. return node.height;
  12412. }
  12413. function nodeInternal(node) {
  12414. return !!node.children;
  12415. }
  12416. function nodeExternal(node) {
  12417. return !node.children;
  12418. }
  12419. function parentValue(evaluate) {
  12420. return (child, parent) => (parent == null ? undefined : evaluate(parent));
  12421. }
  12422. // Walk backwards to find the first slash.
  12423. function nameof(path) {
  12424. let i = path.length;
  12425. while (--i > 0) if (slash(path, i)) break;
  12426. return slashUnescape(path.slice(i + 1));
  12427. }
  12428. // Slashes can be escaped; to determine whether a slash is a path delimiter, we
  12429. // count the number of preceding backslashes escaping the forward slash: an odd
  12430. // number indicates an escaped forward slash.
  12431. function slash(path, i) {
  12432. if (path[i] === "/") {
  12433. let k = 0;
  12434. while (i > 0 && path[--i] === "\\") ++k;
  12435. if ((k & 1) === 0) return true;
  12436. }
  12437. return false;
  12438. }
  12439. // These indexes match the array returned by nodeOutputs. The first two elements
  12440. // are always the name of the output and its column value definition so that
  12441. // the outputs can be passed directly to Object.fromEntries.
  12442. const output_setValues = 2;
  12443. const output_evaluate = 3;
  12444. const output_values = 4;
  12445. function treeOutputs(options, maybeTreeValue) {
  12446. const outputs = [];
  12447. for (const name in options) {
  12448. const value = options[name];
  12449. const treeValue = maybeTreeValue(value);
  12450. if (treeValue !== undefined) {
  12451. outputs.push([name, ...column(value), treeValue]);
  12452. }
  12453. }
  12454. return outputs;
  12455. }
  12456. function tree(
  12457. data,
  12458. {
  12459. fill,
  12460. stroke,
  12461. strokeWidth,
  12462. strokeOpacity,
  12463. strokeLinejoin,
  12464. strokeLinecap,
  12465. strokeMiterlimit,
  12466. strokeDasharray,
  12467. strokeDashoffset,
  12468. marker,
  12469. markerStart = marker,
  12470. markerEnd = marker,
  12471. dot: dotDot = isNoneish(markerStart) && isNoneish(markerEnd),
  12472. text: textText = "node:name",
  12473. textStroke = "var(--plot-background)",
  12474. title = "node:path",
  12475. dx,
  12476. dy,
  12477. textAnchor,
  12478. treeLayout = d3.tree,
  12479. textLayout = treeLayout === d3.tree || treeLayout === d3.cluster ? "mirrored" : "normal",
  12480. tip,
  12481. ...options
  12482. } = {}
  12483. ) {
  12484. if (dx === undefined) dx = maybeTreeAnchor(options.treeAnchor).dx;
  12485. if (textAnchor !== undefined) throw new Error("textAnchor is not a configurable tree option");
  12486. textLayout = keyword(textLayout, "textLayout", ["mirrored", "normal"]);
  12487. function treeText(textOptions) {
  12488. return text(
  12489. data,
  12490. treeNode({
  12491. treeLayout,
  12492. text: textText,
  12493. fill: fill === undefined ? "currentColor" : fill,
  12494. stroke: textStroke,
  12495. dx,
  12496. dy,
  12497. title,
  12498. ...textOptions,
  12499. ...options
  12500. })
  12501. );
  12502. }
  12503. return marks(
  12504. link(
  12505. data,
  12506. treeLink({
  12507. treeLayout,
  12508. markerStart,
  12509. markerEnd,
  12510. stroke: stroke !== undefined ? stroke : fill === undefined ? "node:internal" : fill,
  12511. strokeWidth,
  12512. strokeOpacity,
  12513. strokeLinejoin,
  12514. strokeLinecap,
  12515. strokeMiterlimit,
  12516. strokeDasharray,
  12517. strokeDashoffset,
  12518. ...options
  12519. })
  12520. ),
  12521. dotDot
  12522. ? dot(data, treeNode({treeLayout, fill: fill === undefined ? "node:internal" : fill, title, tip, ...options}))
  12523. : null,
  12524. textText != null
  12525. ? textLayout === "mirrored"
  12526. ? [
  12527. treeText({textAnchor: "start", treeFilter: "node:external"}),
  12528. treeText({textAnchor: "end", treeFilter: "node:internal", dx: -dx})
  12529. ]
  12530. : treeText()
  12531. : null
  12532. );
  12533. }
  12534. function cluster(data, options) {
  12535. return tree(data, {...options, treeLayout: d3.cluster});
  12536. }
  12537. const waffleDefaults = {
  12538. ariaLabel: "waffle"
  12539. };
  12540. class WaffleX extends BarX {
  12541. constructor(data, {unit = 1, gap = 1, round, multiple, ...options} = {}) {
  12542. super(data, wafflePolygon("x", options), waffleDefaults);
  12543. this.unit = Math.max(0, unit);
  12544. this.gap = +gap;
  12545. this.round = maybeRound(round);
  12546. this.multiple = maybeMultiple(multiple);
  12547. }
  12548. }
  12549. class WaffleY extends BarY {
  12550. constructor(data, {unit = 1, gap = 1, round, multiple, ...options} = {}) {
  12551. super(data, wafflePolygon("y", options), waffleDefaults);
  12552. this.unit = Math.max(0, unit);
  12553. this.gap = +gap;
  12554. this.round = maybeRound(round);
  12555. this.multiple = maybeMultiple(multiple);
  12556. }
  12557. }
  12558. function wafflePolygon(y, options) {
  12559. const x = y === "y" ? "x" : "y";
  12560. const y1 = `${y}1`;
  12561. const y2 = `${y}2`;
  12562. return initializer(waffleRender(options), function (data, facets, channels, scales, dimensions) {
  12563. const {round, unit} = this;
  12564. const Y1 = channels[y1].value;
  12565. const Y2 = channels[y2].value;
  12566. // We might not use all the available bandwidth if the cells don’t fit evenly.
  12567. const xy = valueObject({...(x in channels && {[x]: channels[x]}), [y1]: channels[y1], [y2]: channels[y2]}, scales);
  12568. const barwidth = this[y === "y" ? "_width" : "_height"](scales, xy, dimensions);
  12569. const barx = this[y === "y" ? "_x" : "_y"](scales, xy, dimensions);
  12570. // The length of a unit along y in pixels.
  12571. const scale = unit * scaleof(scales.scales[y]);
  12572. // The number of cells on each row (or column) of the waffle.
  12573. const {multiple = Math.max(1, Math.floor(Math.sqrt(barwidth / scale)))} = this;
  12574. // The outer size of each square cell, in pixels, including the gap.
  12575. const cx = Math.min(barwidth / multiple, scale * multiple);
  12576. const cy = scale * multiple;
  12577. // The reference position.
  12578. const tx = (barwidth - multiple * cx) / 2;
  12579. const x0 = typeof barx === "function" ? (i) => barx(i) + tx : barx + tx;
  12580. const y0 = scales[y](0);
  12581. // TODO insets?
  12582. const transform = y === "y" ? ([x, y]) => [x * cx, -y * cy] : ([x, y]) => [y * cy, x * cx];
  12583. const mx = typeof x0 === "function" ? (i) => x0(i) - barwidth / 2 : () => x0;
  12584. const [ix, iy] = y === "y" ? [0, 1] : [1, 0];
  12585. const n = Y2.length;
  12586. const P = new Array(n);
  12587. const X = new Float64Array(n);
  12588. const Y = new Float64Array(n);
  12589. for (let i = 0; i < n; ++i) {
  12590. P[i] = wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple).map(transform);
  12591. const c = P[i].pop(); // extract the transformed centroid
  12592. X[i] = c[ix] + mx(i);
  12593. Y[i] = c[iy] + y0;
  12594. }
  12595. return {
  12596. channels: {
  12597. polygon: {value: P, source: null, filter: null},
  12598. [`c${x}`]: {value: [cx, x0], source: null, filter: null},
  12599. [`c${y}`]: {value: [cy, y0], source: null, filter: null},
  12600. [x]: {value: X, scale: null, source: null},
  12601. [y1]: {value: Y, scale: null, source: channels[y1]},
  12602. [y2]: {value: Y, scale: null, source: channels[y2]}
  12603. }
  12604. };
  12605. });
  12606. }
  12607. function waffleRender({render, ...options}) {
  12608. return {
  12609. ...options,
  12610. render: composeRender(render, function (index, scales, values, dimensions, context) {
  12611. const {gap, rx, ry} = this;
  12612. const {channels, ariaLabel, href, title, ...visualValues} = values;
  12613. const {document} = context;
  12614. const polygon = channels.polygon.value;
  12615. const [cx, x0] = channels.cx.value;
  12616. const [cy, y0] = channels.cy.value;
  12617. // Create a base pattern with shared attributes for cloning.
  12618. const patternId = getPatternId();
  12619. const basePattern = document.createElementNS(d3.namespaces.svg, "pattern");
  12620. basePattern.setAttribute("width", cx);
  12621. basePattern.setAttribute("height", cy);
  12622. basePattern.setAttribute("patternUnits", "userSpaceOnUse");
  12623. const basePatternRect = basePattern.appendChild(document.createElementNS(d3.namespaces.svg, "rect"));
  12624. basePatternRect.setAttribute("x", gap / 2);
  12625. basePatternRect.setAttribute("y", gap / 2);
  12626. basePatternRect.setAttribute("width", cx - gap);
  12627. basePatternRect.setAttribute("height", cy - gap);
  12628. if (rx != null) basePatternRect.setAttribute("rx", rx);
  12629. if (ry != null) basePatternRect.setAttribute("ry", ry);
  12630. return create("svg:g", context)
  12631. .call(applyIndirectStyles, this, dimensions, context)
  12632. .call(this._transform, this, scales)
  12633. .call((g) =>
  12634. g
  12635. .selectAll()
  12636. .data(index)
  12637. .enter()
  12638. .append(() => basePattern.cloneNode(true))
  12639. .attr("id", (i) => `${patternId}-${i}`)
  12640. .select("rect")
  12641. .call(applyDirectStyles, this)
  12642. .call(applyChannelStyles, this, visualValues)
  12643. )
  12644. .call((g) =>
  12645. g
  12646. .selectAll()
  12647. .data(index)
  12648. .enter()
  12649. .append("path")
  12650. .attr("transform", template`translate(${x0},${y0})`)
  12651. .attr("d", (i) => `M${polygon[i].join("L")}Z`)
  12652. .attr("fill", (i) => `url(#${patternId}-${i})`)
  12653. .attr("stroke", this.stroke == null ? null : "none")
  12654. .call(applyChannelStyles, this, {ariaLabel, href, title})
  12655. )
  12656. .node();
  12657. })
  12658. };
  12659. }
  12660. // A waffle is approximately a rectangular shape, but may have one or two corner
  12661. // cuts if the starting or ending value is not an even multiple of the number of
  12662. // columns (the width of the waffle in cells). We can represent any waffle by
  12663. // 8 points; below is a waffle of five columns representing the interval 2–11:
  12664. //
  12665. // 1-0
  12666. // |•7-------6
  12667. // |• • • • •|
  12668. // 2---3• • •|
  12669. // 4-----5
  12670. //
  12671. // Note that points 0 and 1 always have the same y-value, points 1 and 2 have
  12672. // the same x-value, and so on, so we don’t need to materialize the x- and y-
  12673. // values of all points. Also note that we can’t use the already-projected y-
  12674. // values because these assume that y-values are distributed linearly along y
  12675. // rather than wrapping around in columns.
  12676. //
  12677. // The corner points may be coincident. If the ending value is an even multiple
  12678. // of the number of columns, say representing the interval 2–10, then points 6,
  12679. // 7, and 0 are the same.
  12680. //
  12681. // 1-----0/7/6
  12682. // |• • • • •|
  12683. // 2---3• • •|
  12684. // 4-----5
  12685. //
  12686. // Likewise if the starting value is an even multiple, say representing the
  12687. // interval 0–10, points 2–4 are coincident.
  12688. //
  12689. // 1-----0/7/6
  12690. // |• • • • •|
  12691. // |• • • • •|
  12692. // 4/3/2-----5
  12693. //
  12694. // Waffles can also represent fractional intervals (e.g., 2.4–10.1). These
  12695. // require additional corner cuts, so the implementation below generates a few
  12696. // more points.
  12697. //
  12698. // The last point describes the centroid (used for pointing)
  12699. function wafflePoints(i1, i2, columns) {
  12700. if (i2 < i1) return wafflePoints(i2, i1, columns); // ensure i1 <= i2
  12701. if (i1 < 0) return wafflePointsOffset(i1, i2, columns, Math.ceil(-Math.min(i1, i2) / columns)); // ensure i1 >= 0
  12702. const x1f = Math.floor(i1 % columns);
  12703. const x1c = Math.ceil(i1 % columns);
  12704. const x2f = Math.floor(i2 % columns);
  12705. const x2c = Math.ceil(i2 % columns);
  12706. const y1f = Math.floor(i1 / columns);
  12707. const y1c = Math.ceil(i1 / columns);
  12708. const y2f = Math.floor(i2 / columns);
  12709. const y2c = Math.ceil(i2 / columns);
  12710. const points = [];
  12711. if (y2c > y1c) points.push([0, y1c]);
  12712. points.push([x1f, y1c], [x1f, y1f + (i1 % 1)], [x1c, y1f + (i1 % 1)]);
  12713. if (!(i1 % columns > columns - 1)) {
  12714. points.push([x1c, y1f]);
  12715. if (y2f > y1f) points.push([columns, y1f]);
  12716. }
  12717. if (y2f > y1f) points.push([columns, y2f]);
  12718. points.push([x2c, y2f], [x2c, y2f + (i2 % 1)], [x2f, y2f + (i2 % 1)]);
  12719. if (!(i2 % columns < 1)) {
  12720. points.push([x2f, y2c]);
  12721. if (y2c > y1c) points.push([0, y2c]);
  12722. }
  12723. points.push(waffleCentroid(i1, i2, columns));
  12724. return points;
  12725. }
  12726. function wafflePointsOffset(i1, i2, columns, k) {
  12727. return wafflePoints(i1 + k * columns, i2 + k * columns, columns).map(([x, y]) => [x, y - k]);
  12728. }
  12729. function waffleCentroid(i1, i2, columns) {
  12730. const r = Math.floor(i2 / columns) - Math.floor(i1 / columns);
  12731. return r === 0
  12732. ? // Single row
  12733. waffleRowCentroid(i1, i2, columns)
  12734. : r === 1
  12735. ? // Two incomplete rows; use the midpoint of their overlap if any, otherwise the larger row
  12736. Math.floor(i2 % columns) > Math.ceil(i1 % columns)
  12737. ? [(Math.floor(i2 % columns) + Math.ceil(i1 % columns)) / 2, Math.floor(i2 / columns)]
  12738. : i2 % columns > columns - (i1 % columns)
  12739. ? waffleRowCentroid(i2 - (i2 % columns), i2, columns)
  12740. : waffleRowCentroid(i1, columns * Math.ceil(i1 / columns), columns)
  12741. : // At least one full row; take the midpoint of all the rows that include the middle
  12742. [columns / 2, (Math.round(i1 / columns) + Math.round(i2 / columns)) / 2];
  12743. }
  12744. function waffleRowCentroid(i1, i2, columns) {
  12745. const c = Math.floor(i2) - Math.floor(i1);
  12746. return c === 0
  12747. ? // Single cell
  12748. [Math.floor(i1 % columns) + 0.5, Math.floor(i1 / columns) + (((i1 + i2) / 2) % 1)]
  12749. : c === 1
  12750. ? // Two incomplete cells; use the overlap if large enough, otherwise use the largest
  12751. (i2 % 1) - (i1 % 1) > 0.5
  12752. ? [Math.ceil(i1 % columns), Math.floor(i2 / columns) + ((i1 % 1) + (i2 % 1)) / 2]
  12753. : i2 % 1 > 1 - (i1 % 1)
  12754. ? [Math.floor(i2 % columns) + 0.5, Math.floor(i2 / columns) + (i2 % 1) / 2]
  12755. : [Math.floor(i1 % columns) + 0.5, Math.floor(i1 / columns) + (1 + (i1 % 1)) / 2]
  12756. : // At least one full cell; take the midpoint
  12757. [
  12758. Math.ceil(i1 % columns) + Math.ceil(Math.floor(i2) - Math.ceil(i1)) / 2,
  12759. Math.floor(i1 / columns) + (i2 >= 1 + i1 ? 0.5 : ((i1 + i2) / 2) % 1)
  12760. ];
  12761. }
  12762. function maybeRound(round) {
  12763. if (round === undefined || round === false) return Number;
  12764. if (round === true) return Math.round;
  12765. if (typeof round !== "function") throw new Error(`invalid round: ${round}`);
  12766. return round;
  12767. }
  12768. function maybeMultiple(multiple) {
  12769. return multiple === undefined ? undefined : Math.max(1, Math.floor(multiple));
  12770. }
  12771. function scaleof({domain, range}) {
  12772. return spread(range) / spread(domain);
  12773. }
  12774. function spread(domain) {
  12775. const [min, max] = d3.extent(domain);
  12776. return max - min;
  12777. }
  12778. function waffleX(data, {tip, ...options} = {}) {
  12779. if (!hasXY(options)) options = {...options, y: indexOf, x2: identity$1};
  12780. return new WaffleX(data, {tip: waffleTip(tip), ...maybeStackX(maybeIntervalX(maybeIdentityX(options)))});
  12781. }
  12782. function waffleY(data, {tip, ...options} = {}) {
  12783. if (!hasXY(options)) options = {...options, x: indexOf, y2: identity$1};
  12784. return new WaffleY(data, {tip: waffleTip(tip), ...maybeStackY(maybeIntervalY(maybeIdentityY(options)))});
  12785. }
  12786. /**
  12787. * Waffle tips behave a bit unpredictably because we they are driven by the
  12788. * waffle centroid; you could be hovering over a waffle segment, but more than
  12789. * 40px away from its centroid, or closer to the centroid of another segment.
  12790. * We’d rather show a tip, even if it’s the “wrong” one, so we increase the
  12791. * default maxRadius to Infinity. The “right” way to fix this would be to use
  12792. * signed distance to the waffle geometry rather than the centroid.
  12793. */
  12794. function waffleTip(tip) {
  12795. return tip === true
  12796. ? {maxRadius: Infinity}
  12797. : isObject(tip) && tip.maxRadius === undefined
  12798. ? {...tip, maxRadius: Infinity}
  12799. : undefined;
  12800. }
  12801. function getDefaultExportFromCjs (x) {
  12802. return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
  12803. }
  12804. var searchBounds;
  12805. var hasRequiredSearchBounds;
  12806. function requireSearchBounds () {
  12807. if (hasRequiredSearchBounds) return searchBounds;
  12808. hasRequiredSearchBounds = 1;
  12809. // (a, y, c, l, h) = (array, y[, cmp, lo, hi])
  12810. function ge(a, y, c, l, h) {
  12811. var i = h + 1;
  12812. while (l <= h) {
  12813. var m = (l + h) >>> 1, x = a[m];
  12814. var p = (c !== undefined) ? c(x, y) : (x - y);
  12815. if (p >= 0) { i = m; h = m - 1; } else { l = m + 1; }
  12816. }
  12817. return i;
  12818. }
  12819. function gt(a, y, c, l, h) {
  12820. var i = h + 1;
  12821. while (l <= h) {
  12822. var m = (l + h) >>> 1, x = a[m];
  12823. var p = (c !== undefined) ? c(x, y) : (x - y);
  12824. if (p > 0) { i = m; h = m - 1; } else { l = m + 1; }
  12825. }
  12826. return i;
  12827. }
  12828. function lt(a, y, c, l, h) {
  12829. var i = l - 1;
  12830. while (l <= h) {
  12831. var m = (l + h) >>> 1, x = a[m];
  12832. var p = (c !== undefined) ? c(x, y) : (x - y);
  12833. if (p < 0) { i = m; l = m + 1; } else { h = m - 1; }
  12834. }
  12835. return i;
  12836. }
  12837. function le(a, y, c, l, h) {
  12838. var i = l - 1;
  12839. while (l <= h) {
  12840. var m = (l + h) >>> 1, x = a[m];
  12841. var p = (c !== undefined) ? c(x, y) : (x - y);
  12842. if (p <= 0) { i = m; l = m + 1; } else { h = m - 1; }
  12843. }
  12844. return i;
  12845. }
  12846. function eq(a, y, c, l, h) {
  12847. while (l <= h) {
  12848. var m = (l + h) >>> 1, x = a[m];
  12849. var p = (c !== undefined) ? c(x, y) : (x - y);
  12850. if (p === 0) { return m }
  12851. if (p <= 0) { l = m + 1; } else { h = m - 1; }
  12852. }
  12853. return -1;
  12854. }
  12855. function norm(a, y, c, l, h, f) {
  12856. if (typeof c === 'function') {
  12857. return f(a, y, c, (l === undefined) ? 0 : l | 0, (h === undefined) ? a.length - 1 : h | 0);
  12858. }
  12859. return f(a, y, undefined, (c === undefined) ? 0 : c | 0, (l === undefined) ? a.length - 1 : l | 0);
  12860. }
  12861. searchBounds = {
  12862. ge: function(a, y, c, l, h) { return norm(a, y, c, l, h, ge)},
  12863. gt: function(a, y, c, l, h) { return norm(a, y, c, l, h, gt)},
  12864. lt: function(a, y, c, l, h) { return norm(a, y, c, l, h, lt)},
  12865. le: function(a, y, c, l, h) { return norm(a, y, c, l, h, le)},
  12866. eq: function(a, y, c, l, h) { return norm(a, y, c, l, h, eq)}
  12867. };
  12868. return searchBounds;
  12869. }
  12870. var intervalTree;
  12871. var hasRequiredIntervalTree;
  12872. function requireIntervalTree () {
  12873. if (hasRequiredIntervalTree) return intervalTree;
  12874. hasRequiredIntervalTree = 1;
  12875. var bounds = requireSearchBounds();
  12876. var NOT_FOUND = 0;
  12877. var SUCCESS = 1;
  12878. var EMPTY = 2;
  12879. intervalTree = createWrapper;
  12880. function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) {
  12881. this.mid = mid;
  12882. this.left = left;
  12883. this.right = right;
  12884. this.leftPoints = leftPoints;
  12885. this.rightPoints = rightPoints;
  12886. this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length;
  12887. }
  12888. var proto = IntervalTreeNode.prototype;
  12889. function copy(a, b) {
  12890. a.mid = b.mid;
  12891. a.left = b.left;
  12892. a.right = b.right;
  12893. a.leftPoints = b.leftPoints;
  12894. a.rightPoints = b.rightPoints;
  12895. a.count = b.count;
  12896. }
  12897. function rebuild(node, intervals) {
  12898. var ntree = createIntervalTree(intervals);
  12899. node.mid = ntree.mid;
  12900. node.left = ntree.left;
  12901. node.right = ntree.right;
  12902. node.leftPoints = ntree.leftPoints;
  12903. node.rightPoints = ntree.rightPoints;
  12904. node.count = ntree.count;
  12905. }
  12906. function rebuildWithInterval(node, interval) {
  12907. var intervals = node.intervals([]);
  12908. intervals.push(interval);
  12909. rebuild(node, intervals);
  12910. }
  12911. function rebuildWithoutInterval(node, interval) {
  12912. var intervals = node.intervals([]);
  12913. var idx = intervals.indexOf(interval);
  12914. if(idx < 0) {
  12915. return NOT_FOUND
  12916. }
  12917. intervals.splice(idx, 1);
  12918. rebuild(node, intervals);
  12919. return SUCCESS
  12920. }
  12921. proto.intervals = function(result) {
  12922. result.push.apply(result, this.leftPoints);
  12923. if(this.left) {
  12924. this.left.intervals(result);
  12925. }
  12926. if(this.right) {
  12927. this.right.intervals(result);
  12928. }
  12929. return result
  12930. };
  12931. proto.insert = function(interval) {
  12932. var weight = this.count - this.leftPoints.length;
  12933. this.count += 1;
  12934. if(interval[1] < this.mid) {
  12935. if(this.left) {
  12936. if(4*(this.left.count+1) > 3*(weight+1)) {
  12937. rebuildWithInterval(this, interval);
  12938. } else {
  12939. this.left.insert(interval);
  12940. }
  12941. } else {
  12942. this.left = createIntervalTree([interval]);
  12943. }
  12944. } else if(interval[0] > this.mid) {
  12945. if(this.right) {
  12946. if(4*(this.right.count+1) > 3*(weight+1)) {
  12947. rebuildWithInterval(this, interval);
  12948. } else {
  12949. this.right.insert(interval);
  12950. }
  12951. } else {
  12952. this.right = createIntervalTree([interval]);
  12953. }
  12954. } else {
  12955. var l = bounds.ge(this.leftPoints, interval, compareBegin);
  12956. var r = bounds.ge(this.rightPoints, interval, compareEnd);
  12957. this.leftPoints.splice(l, 0, interval);
  12958. this.rightPoints.splice(r, 0, interval);
  12959. }
  12960. };
  12961. proto.remove = function(interval) {
  12962. var weight = this.count - this.leftPoints;
  12963. if(interval[1] < this.mid) {
  12964. if(!this.left) {
  12965. return NOT_FOUND
  12966. }
  12967. var rw = this.right ? this.right.count : 0;
  12968. if(4 * rw > 3 * (weight-1)) {
  12969. return rebuildWithoutInterval(this, interval)
  12970. }
  12971. var r = this.left.remove(interval);
  12972. if(r === EMPTY) {
  12973. this.left = null;
  12974. this.count -= 1;
  12975. return SUCCESS
  12976. } else if(r === SUCCESS) {
  12977. this.count -= 1;
  12978. }
  12979. return r
  12980. } else if(interval[0] > this.mid) {
  12981. if(!this.right) {
  12982. return NOT_FOUND
  12983. }
  12984. var lw = this.left ? this.left.count : 0;
  12985. if(4 * lw > 3 * (weight-1)) {
  12986. return rebuildWithoutInterval(this, interval)
  12987. }
  12988. var r = this.right.remove(interval);
  12989. if(r === EMPTY) {
  12990. this.right = null;
  12991. this.count -= 1;
  12992. return SUCCESS
  12993. } else if(r === SUCCESS) {
  12994. this.count -= 1;
  12995. }
  12996. return r
  12997. } else {
  12998. if(this.count === 1) {
  12999. if(this.leftPoints[0] === interval) {
  13000. return EMPTY
  13001. } else {
  13002. return NOT_FOUND
  13003. }
  13004. }
  13005. if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) {
  13006. if(this.left && this.right) {
  13007. var p = this;
  13008. var n = this.left;
  13009. while(n.right) {
  13010. p = n;
  13011. n = n.right;
  13012. }
  13013. if(p === this) {
  13014. n.right = this.right;
  13015. } else {
  13016. var l = this.left;
  13017. var r = this.right;
  13018. p.count -= n.count;
  13019. p.right = n.left;
  13020. n.left = l;
  13021. n.right = r;
  13022. }
  13023. copy(this, n);
  13024. this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length;
  13025. } else if(this.left) {
  13026. copy(this, this.left);
  13027. } else {
  13028. copy(this, this.right);
  13029. }
  13030. return SUCCESS
  13031. }
  13032. for(var l = bounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) {
  13033. if(this.leftPoints[l][0] !== interval[0]) {
  13034. break
  13035. }
  13036. if(this.leftPoints[l] === interval) {
  13037. this.count -= 1;
  13038. this.leftPoints.splice(l, 1);
  13039. for(var r = bounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) {
  13040. if(this.rightPoints[r][1] !== interval[1]) {
  13041. break
  13042. } else if(this.rightPoints[r] === interval) {
  13043. this.rightPoints.splice(r, 1);
  13044. return SUCCESS
  13045. }
  13046. }
  13047. }
  13048. }
  13049. return NOT_FOUND
  13050. }
  13051. };
  13052. function reportLeftRange(arr, hi, cb) {
  13053. for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) {
  13054. var r = cb(arr[i]);
  13055. if(r) { return r }
  13056. }
  13057. }
  13058. function reportRightRange(arr, lo, cb) {
  13059. for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) {
  13060. var r = cb(arr[i]);
  13061. if(r) { return r }
  13062. }
  13063. }
  13064. function reportRange(arr, cb) {
  13065. for(var i=0; i<arr.length; ++i) {
  13066. var r = cb(arr[i]);
  13067. if(r) { return r }
  13068. }
  13069. }
  13070. proto.queryPoint = function(x, cb) {
  13071. if(x < this.mid) {
  13072. if(this.left) {
  13073. var r = this.left.queryPoint(x, cb);
  13074. if(r) { return r }
  13075. }
  13076. return reportLeftRange(this.leftPoints, x, cb)
  13077. } else if(x > this.mid) {
  13078. if(this.right) {
  13079. var r = this.right.queryPoint(x, cb);
  13080. if(r) { return r }
  13081. }
  13082. return reportRightRange(this.rightPoints, x, cb)
  13083. } else {
  13084. return reportRange(this.leftPoints, cb)
  13085. }
  13086. };
  13087. proto.queryInterval = function(lo, hi, cb) {
  13088. if(lo < this.mid && this.left) {
  13089. var r = this.left.queryInterval(lo, hi, cb);
  13090. if(r) { return r }
  13091. }
  13092. if(hi > this.mid && this.right) {
  13093. var r = this.right.queryInterval(lo, hi, cb);
  13094. if(r) { return r }
  13095. }
  13096. if(hi < this.mid) {
  13097. return reportLeftRange(this.leftPoints, hi, cb)
  13098. } else if(lo > this.mid) {
  13099. return reportRightRange(this.rightPoints, lo, cb)
  13100. } else {
  13101. return reportRange(this.leftPoints, cb)
  13102. }
  13103. };
  13104. function compareNumbers(a, b) {
  13105. return a - b
  13106. }
  13107. function compareBegin(a, b) {
  13108. var d = a[0] - b[0];
  13109. if(d) { return d }
  13110. return a[1] - b[1]
  13111. }
  13112. function compareEnd(a, b) {
  13113. var d = a[1] - b[1];
  13114. if(d) { return d }
  13115. return a[0] - b[0]
  13116. }
  13117. function createIntervalTree(intervals) {
  13118. if(intervals.length === 0) {
  13119. return null
  13120. }
  13121. var pts = [];
  13122. for(var i=0; i<intervals.length; ++i) {
  13123. pts.push(intervals[i][0], intervals[i][1]);
  13124. }
  13125. pts.sort(compareNumbers);
  13126. var mid = pts[pts.length>>1];
  13127. var leftIntervals = [];
  13128. var rightIntervals = [];
  13129. var centerIntervals = [];
  13130. for(var i=0; i<intervals.length; ++i) {
  13131. var s = intervals[i];
  13132. if(s[1] < mid) {
  13133. leftIntervals.push(s);
  13134. } else if(mid < s[0]) {
  13135. rightIntervals.push(s);
  13136. } else {
  13137. centerIntervals.push(s);
  13138. }
  13139. }
  13140. //Split center intervals
  13141. var leftPoints = centerIntervals;
  13142. var rightPoints = centerIntervals.slice();
  13143. leftPoints.sort(compareBegin);
  13144. rightPoints.sort(compareEnd);
  13145. return new IntervalTreeNode(mid,
  13146. createIntervalTree(leftIntervals),
  13147. createIntervalTree(rightIntervals),
  13148. leftPoints,
  13149. rightPoints)
  13150. }
  13151. //User friendly wrapper that makes it possible to support empty trees
  13152. function IntervalTree(root) {
  13153. this.root = root;
  13154. }
  13155. var tproto = IntervalTree.prototype;
  13156. tproto.insert = function(interval) {
  13157. if(this.root) {
  13158. this.root.insert(interval);
  13159. } else {
  13160. this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval]);
  13161. }
  13162. };
  13163. tproto.remove = function(interval) {
  13164. if(this.root) {
  13165. var r = this.root.remove(interval);
  13166. if(r === EMPTY) {
  13167. this.root = null;
  13168. }
  13169. return r !== NOT_FOUND
  13170. }
  13171. return false
  13172. };
  13173. tproto.queryPoint = function(p, cb) {
  13174. if(this.root) {
  13175. return this.root.queryPoint(p, cb)
  13176. }
  13177. };
  13178. tproto.queryInterval = function(lo, hi, cb) {
  13179. if(lo <= hi && this.root) {
  13180. return this.root.queryInterval(lo, hi, cb)
  13181. }
  13182. };
  13183. Object.defineProperty(tproto, "count", {
  13184. get: function() {
  13185. if(this.root) {
  13186. return this.root.count
  13187. }
  13188. return 0
  13189. }
  13190. });
  13191. Object.defineProperty(tproto, "intervals", {
  13192. get: function() {
  13193. if(this.root) {
  13194. return this.root.intervals([])
  13195. }
  13196. return []
  13197. }
  13198. });
  13199. function createWrapper(intervals) {
  13200. if(!intervals || intervals.length === 0) {
  13201. return new IntervalTree(null)
  13202. }
  13203. return new IntervalTree(createIntervalTree(intervals))
  13204. }
  13205. return intervalTree;
  13206. }
  13207. var intervalTreeExports = requireIntervalTree();
  13208. var IntervalTree = /*@__PURE__*/getDefaultExportFromCjs(intervalTreeExports);
  13209. const anchorXLeft = ({marginLeft}) => [1, marginLeft];
  13210. const anchorXRight = ({width, marginRight}) => [-1, width - marginRight];
  13211. const anchorXMiddle = ({width, marginLeft, marginRight}) => [0, (marginLeft + width - marginRight) / 2];
  13212. const anchorYTop = ({marginTop}) => [1, marginTop];
  13213. const anchorYBottom = ({height, marginBottom}) => [-1, height - marginBottom];
  13214. const anchorYMiddle = ({height, marginTop, marginBottom}) => [0, (marginTop + height - marginBottom) / 2];
  13215. function maybeAnchor(anchor) {
  13216. return typeof anchor === "string" ? {anchor} : anchor;
  13217. }
  13218. function dodgeX(dodgeOptions = {}, options = {}) {
  13219. if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions);
  13220. let {anchor = "left", padding = 1, r = options.r} = maybeAnchor(dodgeOptions);
  13221. switch (`${anchor}`.toLowerCase()) {
  13222. case "left":
  13223. anchor = anchorXLeft;
  13224. break;
  13225. case "right":
  13226. anchor = anchorXRight;
  13227. break;
  13228. case "middle":
  13229. anchor = anchorXMiddle;
  13230. break;
  13231. default:
  13232. throw new Error(`unknown dodge anchor: ${anchor}`);
  13233. }
  13234. return dodge("x", "y", anchor, number$1(padding), r, options);
  13235. }
  13236. function dodgeY(dodgeOptions = {}, options = {}) {
  13237. if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions);
  13238. let {anchor = "bottom", padding = 1, r = options.r} = maybeAnchor(dodgeOptions);
  13239. switch (`${anchor}`.toLowerCase()) {
  13240. case "top":
  13241. anchor = anchorYTop;
  13242. break;
  13243. case "bottom":
  13244. anchor = anchorYBottom;
  13245. break;
  13246. case "middle":
  13247. anchor = anchorYMiddle;
  13248. break;
  13249. default:
  13250. throw new Error(`unknown dodge anchor: ${anchor}`);
  13251. }
  13252. return dodge("y", "x", anchor, number$1(padding), r, options);
  13253. }
  13254. function mergeOptions(options) {
  13255. const {anchor, padding, ...rest} = options;
  13256. const {r} = rest; // don’t consume r; allow it to propagate
  13257. return [{anchor, padding, r}, rest];
  13258. }
  13259. function dodge(y, x, anchor, padding, r, options) {
  13260. if (r != null && typeof r !== "number") {
  13261. let {channels, sort, reverse} = options;
  13262. channels = maybeNamed(channels);
  13263. if (channels?.r === undefined) options = {...options, channels: {...channels, r: {value: r, scale: "r"}}};
  13264. if (sort === undefined && reverse === undefined) options.sort = {channel: "-r"};
  13265. }
  13266. return initializer(options, function (data, facets, channels, scales, dimensions, context) {
  13267. let {[x]: X, r: R} = channels;
  13268. if (!channels[x]) throw new Error(`missing channel: ${x}`);
  13269. ({[x]: X} = applyPosition(channels, scales, context));
  13270. const cr = R ? undefined : r !== undefined ? number$1(r) : this.r !== undefined ? this.r : 3;
  13271. if (R) R = valueof(R.value, scales[R.scale] || identity$1, Float64Array);
  13272. let [ky, ty] = anchor(dimensions);
  13273. const compare = ky ? compareAscending : compareSymmetric;
  13274. const Y = new Float64Array(X.length);
  13275. const radius = R ? (i) => R[i] : () => cr;
  13276. for (let I of facets) {
  13277. const tree = IntervalTree();
  13278. I = I.filter(R ? (i) => finite$1(X[i]) && positive(R[i]) : (i) => finite$1(X[i]));
  13279. const intervals = new Float64Array(2 * I.length + 2);
  13280. for (const i of I) {
  13281. const ri = radius(i);
  13282. const y0 = ky ? ri + padding : 0; // offset baseline for varying radius
  13283. const l = X[i] - ri;
  13284. const h = X[i] + ri;
  13285. // The first two positions are 0 to test placing the dot on the baseline.
  13286. let k = 2;
  13287. // For any previously placed circles that may overlap this circle, compute
  13288. // the y-positions that place this circle tangent to these other circles.
  13289. // https://observablehq.com/@mbostock/circle-offset-along-line
  13290. tree.queryInterval(l - padding, h + padding, ([, , j]) => {
  13291. const yj = Y[j] - y0;
  13292. const dx = X[i] - X[j];
  13293. const dr = padding + (R ? R[i] + R[j] : 2 * cr);
  13294. const dy = Math.sqrt(dr * dr - dx * dx);
  13295. intervals[k++] = yj - dy;
  13296. intervals[k++] = yj + dy;
  13297. });
  13298. // Find the best y-value where this circle can fit.
  13299. let candidates = intervals.slice(0, k);
  13300. if (ky) candidates = candidates.filter((y) => y >= 0);
  13301. out: for (const y of candidates.sort(compare)) {
  13302. for (let j = 0; j < k; j += 2) {
  13303. if (intervals[j] + 1e-6 < y && y < intervals[j + 1] - 1e-6) {
  13304. continue out;
  13305. }
  13306. }
  13307. Y[i] = y + y0;
  13308. break;
  13309. }
  13310. // Insert the placed circle into the interval tree.
  13311. tree.insert([l, h, i]);
  13312. }
  13313. }
  13314. if (!ky) ky = 1;
  13315. for (const I of facets) {
  13316. for (const i of I) {
  13317. Y[i] = Y[i] * ky + ty;
  13318. }
  13319. }
  13320. return {
  13321. data,
  13322. facets,
  13323. channels: {
  13324. [y]: {value: Y, source: null}, // don’t show in tooltip
  13325. [x]: {value: X, source: channels[x]},
  13326. ...(R && {r: {value: R, source: channels.r}})
  13327. }
  13328. };
  13329. });
  13330. }
  13331. function compareSymmetric(a, b) {
  13332. return Math.abs(a) - Math.abs(b);
  13333. }
  13334. function compareAscending(a, b) {
  13335. return a - b;
  13336. }
  13337. function normalizeX(basis, options) {
  13338. if (arguments.length === 1) ({basis, ...options} = basis);
  13339. return mapX(normalize(basis), options);
  13340. }
  13341. function normalizeY(basis, options) {
  13342. if (arguments.length === 1) ({basis, ...options} = basis);
  13343. return mapY(normalize(basis), options);
  13344. }
  13345. function normalize(basis) {
  13346. if (basis === undefined) return normalizeFirst;
  13347. if (typeof basis === "function") return normalizeBasis(taker(basis));
  13348. if (/^p\d{2}$/i.test(basis)) return normalizeAccessor(percentile(basis));
  13349. switch (`${basis}`.toLowerCase()) {
  13350. case "deviation":
  13351. return normalizeDeviation;
  13352. case "first":
  13353. return normalizeFirst;
  13354. case "last":
  13355. return normalizeLast;
  13356. case "max":
  13357. return normalizeMax;
  13358. case "mean":
  13359. return normalizeMean;
  13360. case "median":
  13361. return normalizeMedian;
  13362. case "min":
  13363. return normalizeMin;
  13364. case "sum":
  13365. return normalizeSum;
  13366. case "extent":
  13367. return normalizeExtent;
  13368. }
  13369. throw new Error(`invalid basis: ${basis}`);
  13370. }
  13371. function normalizeBasis(basis) {
  13372. return {
  13373. mapIndex(I, S, T) {
  13374. const b = +basis(I, S);
  13375. for (const i of I) {
  13376. T[i] = S[i] === null ? NaN : S[i] / b;
  13377. }
  13378. }
  13379. };
  13380. }
  13381. function normalizeAccessor(f) {
  13382. return normalizeBasis((I, S) => f(I, (i) => S[i]));
  13383. }
  13384. const normalizeExtent = {
  13385. mapIndex(I, S, T) {
  13386. const [s1, s2] = d3.extent(I, (i) => S[i]);
  13387. const d = s2 - s1;
  13388. for (const i of I) {
  13389. T[i] = S[i] === null ? NaN : (S[i] - s1) / d;
  13390. }
  13391. }
  13392. };
  13393. const normalizeFirst = normalizeBasis((I, S) => {
  13394. for (let i = 0; i < I.length; ++i) {
  13395. const s = S[I[i]];
  13396. if (defined(s)) return s;
  13397. }
  13398. });
  13399. const normalizeLast = normalizeBasis((I, S) => {
  13400. for (let i = I.length - 1; i >= 0; --i) {
  13401. const s = S[I[i]];
  13402. if (defined(s)) return s;
  13403. }
  13404. });
  13405. const normalizeDeviation = {
  13406. mapIndex(I, S, T) {
  13407. const m = d3.mean(I, (i) => S[i]);
  13408. const d = d3.deviation(I, (i) => S[i]);
  13409. for (const i of I) {
  13410. T[i] = S[i] === null ? NaN : d ? (S[i] - m) / d : 0;
  13411. }
  13412. }
  13413. };
  13414. const normalizeMax = normalizeAccessor(d3.max);
  13415. const normalizeMean = normalizeAccessor(d3.mean);
  13416. const normalizeMedian = normalizeAccessor(d3.median);
  13417. const normalizeMin = normalizeAccessor(d3.min);
  13418. const normalizeSum = normalizeAccessor(d3.sum);
  13419. function shiftX(interval, options) {
  13420. return shiftK("x", interval, options);
  13421. }
  13422. function shiftY(interval, options) {
  13423. return shiftK("y", interval, options);
  13424. }
  13425. function shiftK(x, interval, options = {}) {
  13426. let offset;
  13427. let k = 1;
  13428. if (typeof interval === "number") {
  13429. k = interval;
  13430. offset = (x, k) => +x + k;
  13431. } else {
  13432. if (typeof interval === "string") {
  13433. const sign = interval.startsWith("-") ? -1 : 1;
  13434. [interval, k] = parseTimeInterval(interval.replace(/^[+-]/, ""));
  13435. k *= sign;
  13436. }
  13437. interval = maybeInterval(interval);
  13438. offset = (x, k) => interval.offset(x, k);
  13439. }
  13440. const x1 = `${x}1`;
  13441. const x2 = `${x}2`;
  13442. const mapped = map(
  13443. {
  13444. [x1]: (D) => D.map((d) => offset(d, k)),
  13445. [x2]: (D) => D
  13446. },
  13447. options
  13448. );
  13449. const t = mapped[x2].transform;
  13450. mapped[x2].transform = () => {
  13451. const V = t();
  13452. const [x0, x1] = d3.extent(V);
  13453. V.domain = k < 0 ? [x0, offset(x1, k)] : [offset(x0, k), x1];
  13454. return V;
  13455. };
  13456. return mapped;
  13457. }
  13458. function select(selector, options = {}) {
  13459. // If specified selector is a string or function, it’s a selector without an
  13460. // input channel such as first or last.
  13461. if (typeof selector === "string") {
  13462. switch (selector.toLowerCase()) {
  13463. case "first":
  13464. return selectFirst(options);
  13465. case "last":
  13466. return selectLast(options);
  13467. }
  13468. }
  13469. if (typeof selector === "function") {
  13470. return selectChannel(null, selector, options);
  13471. }
  13472. // Otherwise the selector is an option {name: value} where name is a channel
  13473. // name and value is a selector definition that additionally takes the given
  13474. // channel values as input. The selector object must have exactly one key.
  13475. let key, value;
  13476. for (key in selector) {
  13477. if (value !== undefined) throw new Error("ambiguous selector; multiple inputs");
  13478. value = maybeSelector(selector[key]);
  13479. }
  13480. if (value === undefined) throw new Error(`invalid selector: ${selector}`);
  13481. return selectChannel(key, value, options);
  13482. }
  13483. function maybeSelector(selector) {
  13484. if (typeof selector === "function") return selector;
  13485. switch (`${selector}`.toLowerCase()) {
  13486. case "min":
  13487. return selectorMin;
  13488. case "max":
  13489. return selectorMax;
  13490. }
  13491. throw new Error(`unknown selector: ${selector}`);
  13492. }
  13493. function selectFirst(options) {
  13494. return selectChannel(null, selectorFirst, options);
  13495. }
  13496. function selectLast(options) {
  13497. return selectChannel(null, selectorLast, options);
  13498. }
  13499. function selectMinX(options) {
  13500. return selectChannel("x", selectorMin, options);
  13501. }
  13502. function selectMinY(options) {
  13503. return selectChannel("y", selectorMin, options);
  13504. }
  13505. function selectMaxX(options) {
  13506. return selectChannel("x", selectorMax, options);
  13507. }
  13508. function selectMaxY(options) {
  13509. return selectChannel("y", selectorMax, options);
  13510. }
  13511. function* selectorFirst(I) {
  13512. yield I[0];
  13513. }
  13514. function* selectorLast(I) {
  13515. yield I[I.length - 1];
  13516. }
  13517. function* selectorMin(I, X) {
  13518. yield d3.least(I, (i) => X[i]);
  13519. }
  13520. function* selectorMax(I, X) {
  13521. yield d3.greatest(I, (i) => X[i]);
  13522. }
  13523. function selectChannel(v, selector, options) {
  13524. if (v != null) {
  13525. if (options[v] == null) throw new Error(`missing channel: ${v}`);
  13526. v = options[v];
  13527. }
  13528. const z = maybeZ(options);
  13529. return basic(options, (data, facets) => {
  13530. const Z = valueof(data, z);
  13531. const V = valueof(data, v);
  13532. const selectFacets = [];
  13533. for (const facet of facets) {
  13534. const selectFacet = [];
  13535. for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) {
  13536. for (const i of selector(I, V)) {
  13537. selectFacet.push(i);
  13538. }
  13539. }
  13540. selectFacets.push(selectFacet);
  13541. }
  13542. return {data, facets: selectFacets};
  13543. });
  13544. }
  13545. // Note: this side effect avoids a circular dependency.
  13546. Mark.prototype.plot = function ({marks = [], ...options} = {}) {
  13547. return plot({...options, marks: [...marks, this]});
  13548. };
  13549. exports.Area = Area;
  13550. exports.Arrow = Arrow;
  13551. exports.BarX = BarX;
  13552. exports.BarY = BarY;
  13553. exports.Cell = Cell;
  13554. exports.Contour = Contour;
  13555. exports.Density = Density;
  13556. exports.Dot = Dot;
  13557. exports.Frame = Frame;
  13558. exports.Geo = Geo;
  13559. exports.Hexgrid = Hexgrid;
  13560. exports.Image = Image;
  13561. exports.Line = Line;
  13562. exports.Link = Link;
  13563. exports.Mark = Mark;
  13564. exports.Raster = Raster;
  13565. exports.Rect = Rect;
  13566. exports.RuleX = RuleX;
  13567. exports.RuleY = RuleY;
  13568. exports.Text = Text;
  13569. exports.TickX = TickX;
  13570. exports.TickY = TickY;
  13571. exports.Tip = Tip;
  13572. exports.Vector = Vector;
  13573. exports.WaffleX = WaffleX;
  13574. exports.WaffleY = WaffleY;
  13575. exports.area = area;
  13576. exports.areaX = areaX;
  13577. exports.areaY = areaY;
  13578. exports.arrow = arrow;
  13579. exports.auto = auto;
  13580. exports.autoSpec = autoSpec;
  13581. exports.axisFx = axisFx;
  13582. exports.axisFy = axisFy;
  13583. exports.axisX = axisX;
  13584. exports.axisY = axisY;
  13585. exports.barX = barX;
  13586. exports.barY = barY;
  13587. exports.bin = bin;
  13588. exports.binX = binX;
  13589. exports.binY = binY;
  13590. exports.bollinger = bollinger;
  13591. exports.bollingerX = bollingerX;
  13592. exports.bollingerY = bollingerY;
  13593. exports.boxX = boxX;
  13594. exports.boxY = boxY;
  13595. exports.cell = cell;
  13596. exports.cellX = cellX;
  13597. exports.cellY = cellY;
  13598. exports.centroid = centroid;
  13599. exports.circle = circle;
  13600. exports.cluster = cluster;
  13601. exports.column = column;
  13602. exports.contour = contour;
  13603. exports.crosshair = crosshair;
  13604. exports.crosshairX = crosshairX;
  13605. exports.crosshairY = crosshairY;
  13606. exports.delaunayLink = delaunayLink;
  13607. exports.delaunayMesh = delaunayMesh;
  13608. exports.density = density;
  13609. exports.differenceX = differenceX;
  13610. exports.differenceY = differenceY;
  13611. exports.dodgeX = dodgeX;
  13612. exports.dodgeY = dodgeY;
  13613. exports.dot = dot;
  13614. exports.dotX = dotX;
  13615. exports.dotY = dotY;
  13616. exports.filter = filter;
  13617. exports.find = find;
  13618. exports.formatIsoDate = formatIsoDate;
  13619. exports.formatMonth = formatMonth;
  13620. exports.formatNumber = formatNumber;
  13621. exports.formatWeekday = formatWeekday;
  13622. exports.frame = frame;
  13623. exports.geo = geo;
  13624. exports.geoCentroid = geoCentroid;
  13625. exports.graticule = graticule;
  13626. exports.gridFx = gridFx;
  13627. exports.gridFy = gridFy;
  13628. exports.gridX = gridX;
  13629. exports.gridY = gridY;
  13630. exports.group = group;
  13631. exports.groupX = groupX;
  13632. exports.groupY = groupY;
  13633. exports.groupZ = groupZ$1;
  13634. exports.hexagon = hexagon;
  13635. exports.hexbin = hexbin;
  13636. exports.hexgrid = hexgrid;
  13637. exports.hull = hull;
  13638. exports.identity = identity$1;
  13639. exports.image = image;
  13640. exports.indexOf = indexOf;
  13641. exports.initializer = initializer;
  13642. exports.interpolateNearest = interpolateNearest;
  13643. exports.interpolateNone = interpolateNone;
  13644. exports.interpolatorBarycentric = interpolatorBarycentric;
  13645. exports.interpolatorRandomWalk = interpolatorRandomWalk;
  13646. exports.legend = legend;
  13647. exports.line = line;
  13648. exports.lineX = lineX;
  13649. exports.lineY = lineY;
  13650. exports.linearRegressionX = linearRegressionX;
  13651. exports.linearRegressionY = linearRegressionY;
  13652. exports.link = link;
  13653. exports.map = map;
  13654. exports.mapX = mapX;
  13655. exports.mapY = mapY;
  13656. exports.marks = marks;
  13657. exports.normalize = normalize;
  13658. exports.normalizeX = normalizeX;
  13659. exports.normalizeY = normalizeY;
  13660. exports.numberInterval = numberInterval;
  13661. exports.plot = plot;
  13662. exports.pointer = pointer;
  13663. exports.pointerX = pointerX;
  13664. exports.pointerY = pointerY;
  13665. exports.raster = raster;
  13666. exports.rect = rect;
  13667. exports.rectX = rectX;
  13668. exports.rectY = rectY;
  13669. exports.reverse = reverse;
  13670. exports.ruleX = ruleX;
  13671. exports.ruleY = ruleY;
  13672. exports.scale = scale;
  13673. exports.select = select;
  13674. exports.selectFirst = selectFirst;
  13675. exports.selectLast = selectLast;
  13676. exports.selectMaxX = selectMaxX;
  13677. exports.selectMaxY = selectMaxY;
  13678. exports.selectMinX = selectMinX;
  13679. exports.selectMinY = selectMinY;
  13680. exports.shiftX = shiftX;
  13681. exports.shiftY = shiftY;
  13682. exports.shuffle = shuffle;
  13683. exports.sort = sort;
  13684. exports.sphere = sphere;
  13685. exports.spike = spike;
  13686. exports.stackX = stackX;
  13687. exports.stackX1 = stackX1;
  13688. exports.stackX2 = stackX2;
  13689. exports.stackY = stackY;
  13690. exports.stackY1 = stackY1;
  13691. exports.stackY2 = stackY2;
  13692. exports.text = text;
  13693. exports.textX = textX;
  13694. exports.textY = textY;
  13695. exports.tickX = tickX;
  13696. exports.tickY = tickY;
  13697. exports.timeInterval = timeInterval;
  13698. exports.tip = tip;
  13699. exports.transform = basic;
  13700. exports.tree = tree;
  13701. exports.treeLink = treeLink;
  13702. exports.treeNode = treeNode;
  13703. exports.utcInterval = utcInterval;
  13704. exports.valueof = valueof;
  13705. exports.vector = vector;
  13706. exports.vectorX = vectorX;
  13707. exports.vectorY = vectorY;
  13708. exports.version = version;
  13709. exports.voronoi = voronoi;
  13710. exports.voronoiMesh = voronoiMesh;
  13711. exports.waffleX = waffleX;
  13712. exports.waffleY = waffleY;
  13713. exports.window = window$1;
  13714. exports.windowX = windowX;
  13715. exports.windowY = windowY;
  13716. }));