| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164 |
- /*!
- * @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG.
- * @version 3.2.0
- * https://svgjs.dev/
- *
- * @copyright Wout Fierens <wout@mick-wout.com>
- * @license MIT
- *
- * BUILT: Mon Jun 12 2023 10:34:51 GMT+0200 (Central European Summer Time)
- */;
- 'use strict';
- Object.defineProperty(exports, '__esModule', { value: true });
- const methods$1 = {};
- const names = [];
- function registerMethods(name, m) {
- if (Array.isArray(name)) {
- for (const _name of name) {
- registerMethods(_name, m);
- }
- return;
- }
- if (typeof name === 'object') {
- for (const _name in name) {
- registerMethods(_name, name[_name]);
- }
- return;
- }
- addMethodNames(Object.getOwnPropertyNames(m));
- methods$1[name] = Object.assign(methods$1[name] || {}, m);
- }
- function getMethodsFor(name) {
- return methods$1[name] || {};
- }
- function getMethodNames() {
- return [...new Set(names)];
- }
- function addMethodNames(_names) {
- names.push(..._names);
- }
- // Map function
- function map(array, block) {
- let i;
- const il = array.length;
- const result = [];
- for (i = 0; i < il; i++) {
- result.push(block(array[i]));
- }
- return result;
- } // Filter function
- function filter(array, block) {
- let i;
- const il = array.length;
- const result = [];
- for (i = 0; i < il; i++) {
- if (block(array[i])) {
- result.push(array[i]);
- }
- }
- return result;
- } // Degrees to radians
- function radians(d) {
- return d % 360 * Math.PI / 180;
- } // Radians to degrees
- function degrees(r) {
- return r * 180 / Math.PI % 360;
- } // Convert dash-separated-string to camelCase
- function camelCase(s) {
- return s.toLowerCase().replace(/-(.)/g, function (m, g) {
- return g.toUpperCase();
- });
- } // Convert camel cased string to dash separated
- function unCamelCase(s) {
- return s.replace(/([A-Z])/g, function (m, g) {
- return '-' + g.toLowerCase();
- });
- } // Capitalize first letter of a string
- function capitalize(s) {
- return s.charAt(0).toUpperCase() + s.slice(1);
- } // Calculate proportional width and height values when necessary
- function proportionalSize(element, width, height, box) {
- if (width == null || height == null) {
- box = box || element.bbox();
- if (width == null) {
- width = box.width / box.height * height;
- } else if (height == null) {
- height = box.height / box.width * width;
- }
- }
- return {
- width: width,
- height: height
- };
- }
- /**
- * This function adds support for string origins.
- * It searches for an origin in o.origin o.ox and o.originX.
- * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50
- **/
- function getOrigin(o, element) {
- const origin = o.origin; // First check if origin is in ox or originX
- let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center';
- let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'; // Then check if origin was used and overwrite in that case
- if (origin != null) {
- [ox, oy] = Array.isArray(origin) ? origin : typeof origin === 'object' ? [origin.x, origin.y] : [origin, origin];
- } // Make sure to only call bbox when actually needed
- const condX = typeof ox === 'string';
- const condY = typeof oy === 'string';
- if (condX || condY) {
- const {
- height,
- width,
- x,
- y
- } = element.bbox(); // And only overwrite if string was passed for this specific axis
- if (condX) {
- ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2;
- }
- if (condY) {
- oy = oy.includes('top') ? y : oy.includes('bottom') ? y + height : y + height / 2;
- }
- } // Return the origin as it is if it wasn't a string
- return [ox, oy];
- }
- var utils = {
- __proto__: null,
- map: map,
- filter: filter,
- radians: radians,
- degrees: degrees,
- camelCase: camelCase,
- unCamelCase: unCamelCase,
- capitalize: capitalize,
- proportionalSize: proportionalSize,
- getOrigin: getOrigin
- };
- // Default namespaces
- const svg = 'http://www.w3.org/2000/svg';
- const html = 'http://www.w3.org/1999/xhtml';
- const xmlns = 'http://www.w3.org/2000/xmlns/';
- const xlink = 'http://www.w3.org/1999/xlink';
- const svgjs = 'http://svgjs.dev/svgjs';
- var namespaces = {
- __proto__: null,
- svg: svg,
- html: html,
- xmlns: xmlns,
- xlink: xlink,
- svgjs: svgjs
- };
- const globals = {
- window: typeof window === 'undefined' ? null : window,
- document: typeof document === 'undefined' ? null : document
- };
- function registerWindow(win = null, doc = null) {
- globals.window = win;
- globals.document = doc;
- }
- const save = {};
- function saveWindow() {
- save.window = globals.window;
- save.document = globals.document;
- }
- function restoreWindow() {
- globals.window = save.window;
- globals.document = save.document;
- }
- function withWindow(win, fn) {
- saveWindow();
- registerWindow(win, win.document);
- fn(win, win.document);
- restoreWindow();
- }
- function getWindow() {
- return globals.window;
- }
- class Base {// constructor (node/*, {extensions = []} */) {
- // // this.tags = []
- // //
- // // for (let extension of extensions) {
- // // extension.setup.call(this, node)
- // // this.tags.push(extension.name)
- // // }
- // }
- }
- const elements = {};
- const root = '___SYMBOL___ROOT___'; // Method for element creation
- function create(name, ns = svg) {
- // create element
- return globals.document.createElementNS(ns, name);
- }
- function makeInstance(element, isHTML = false) {
- if (element instanceof Base) return element;
- if (typeof element === 'object') {
- return adopter(element);
- }
- if (element == null) {
- return new elements[root]();
- }
- if (typeof element === 'string' && element.charAt(0) !== '<') {
- return adopter(globals.document.querySelector(element));
- } // Make sure, that HTML elements are created with the correct namespace
- const wrapper = isHTML ? globals.document.createElement('div') : create('svg');
- wrapper.innerHTML = element; // We can use firstChild here because we know,
- // that the first char is < and thus an element
- element = adopter(wrapper.firstChild); // make sure, that element doesn't have its wrapper attached
- wrapper.removeChild(wrapper.firstChild);
- return element;
- }
- function nodeOrNew(name, node) {
- return node && node.ownerDocument && node instanceof node.ownerDocument.defaultView.Node ? node : create(name);
- } // Adopt existing svg elements
- function adopt(node) {
- // check for presence of node
- if (!node) return null; // make sure a node isn't already adopted
- if (node.instance instanceof Base) return node.instance;
- if (node.nodeName === '#document-fragment') {
- return new elements.Fragment(node);
- } // initialize variables
- let className = capitalize(node.nodeName || 'Dom'); // Make sure that gradients are adopted correctly
- if (className === 'LinearGradient' || className === 'RadialGradient') {
- className = 'Gradient'; // Fallback to Dom if element is not known
- } else if (!elements[className]) {
- className = 'Dom';
- }
- return new elements[className](node);
- }
- let adopter = adopt;
- function mockAdopt(mock = adopt) {
- adopter = mock;
- }
- function register(element, name = element.name, asRoot = false) {
- elements[name] = element;
- if (asRoot) elements[root] = element;
- addMethodNames(Object.getOwnPropertyNames(element.prototype));
- return element;
- }
- function getClass(name) {
- return elements[name];
- } // Element id sequence
- let did = 1000; // Get next named element id
- function eid(name) {
- return 'Svgjs' + capitalize(name) + did++;
- } // Deep new id assignment
- function assignNewId(node) {
- // do the same for SVG child nodes as well
- for (let i = node.children.length - 1; i >= 0; i--) {
- assignNewId(node.children[i]);
- }
- if (node.id) {
- node.id = eid(node.nodeName);
- return node;
- }
- return node;
- } // Method for extending objects
- function extend(modules, methods) {
- let key, i;
- modules = Array.isArray(modules) ? modules : [modules];
- for (i = modules.length - 1; i >= 0; i--) {
- for (key in methods) {
- modules[i].prototype[key] = methods[key];
- }
- }
- }
- function wrapWithAttrCheck(fn) {
- return function (...args) {
- const o = args[args.length - 1];
- if (o && o.constructor === Object && !(o instanceof Array)) {
- return fn.apply(this, args.slice(0, -1)).attr(o);
- } else {
- return fn.apply(this, args);
- }
- };
- }
- function siblings() {
- return this.parent().children();
- } // Get the current position siblings
- function position() {
- return this.parent().index(this);
- } // Get the next element (will return null if there is none)
- function next() {
- return this.siblings()[this.position() + 1];
- } // Get the next element (will return null if there is none)
- function prev() {
- return this.siblings()[this.position() - 1];
- } // Send given element one step forward
- function forward() {
- const i = this.position();
- const p = this.parent(); // move node one step forward
- p.add(this.remove(), i + 1);
- return this;
- } // Send given element one step backward
- function backward() {
- const i = this.position();
- const p = this.parent();
- p.add(this.remove(), i ? i - 1 : 0);
- return this;
- } // Send given element all the way to the front
- function front() {
- const p = this.parent(); // Move node forward
- p.add(this.remove());
- return this;
- } // Send given element all the way to the back
- function back() {
- const p = this.parent(); // Move node back
- p.add(this.remove(), 0);
- return this;
- } // Inserts a given element before the targeted element
- function before(element) {
- element = makeInstance(element);
- element.remove();
- const i = this.position();
- this.parent().add(element, i);
- return this;
- } // Inserts a given element after the targeted element
- function after(element) {
- element = makeInstance(element);
- element.remove();
- const i = this.position();
- this.parent().add(element, i + 1);
- return this;
- }
- function insertBefore(element) {
- element = makeInstance(element);
- element.before(this);
- return this;
- }
- function insertAfter(element) {
- element = makeInstance(element);
- element.after(this);
- return this;
- }
- registerMethods('Dom', {
- siblings,
- position,
- next,
- prev,
- forward,
- backward,
- front,
- back,
- before,
- after,
- insertBefore,
- insertAfter
- });
- // Parse unit value
- const numberAndUnit = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i; // Parse hex value
- const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; // Parse rgb value
- const rgb = /rgb\((\d+),(\d+),(\d+)\)/; // Parse reference id
- const reference = /(#[a-z_][a-z0-9\-_]*)/i; // splits a transformation chain
- const transforms = /\)\s*,?\s*/; // Whitespace
- const whitespace = /\s/g; // Test hex value
- const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i; // Test rgb value
- const isRgb = /^rgb\(/; // Test for blank string
- const isBlank = /^(\s+)?$/; // Test for numeric string
- const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; // Test for image url
- const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i; // split at whitespace and comma
- const delimiter = /[\s,]+/; // Test for path letter
- const isPathLetter = /[MLHVCSQTAZ]/i;
- var regex = {
- __proto__: null,
- numberAndUnit: numberAndUnit,
- hex: hex,
- rgb: rgb,
- reference: reference,
- transforms: transforms,
- whitespace: whitespace,
- isHex: isHex,
- isRgb: isRgb,
- isBlank: isBlank,
- isNumber: isNumber,
- isImage: isImage,
- delimiter: delimiter,
- isPathLetter: isPathLetter
- };
- function classes() {
- const attr = this.attr('class');
- return attr == null ? [] : attr.trim().split(delimiter);
- } // Return true if class exists on the node, false otherwise
- function hasClass(name) {
- return this.classes().indexOf(name) !== -1;
- } // Add class to the node
- function addClass(name) {
- if (!this.hasClass(name)) {
- const array = this.classes();
- array.push(name);
- this.attr('class', array.join(' '));
- }
- return this;
- } // Remove class from the node
- function removeClass(name) {
- if (this.hasClass(name)) {
- this.attr('class', this.classes().filter(function (c) {
- return c !== name;
- }).join(' '));
- }
- return this;
- } // Toggle the presence of a class on the node
- function toggleClass(name) {
- return this.hasClass(name) ? this.removeClass(name) : this.addClass(name);
- }
- registerMethods('Dom', {
- classes,
- hasClass,
- addClass,
- removeClass,
- toggleClass
- });
- function css(style, val) {
- const ret = {};
- if (arguments.length === 0) {
- // get full style as object
- this.node.style.cssText.split(/\s*;\s*/).filter(function (el) {
- return !!el.length;
- }).forEach(function (el) {
- const t = el.split(/\s*:\s*/);
- ret[t[0]] = t[1];
- });
- return ret;
- }
- if (arguments.length < 2) {
- // get style properties as array
- if (Array.isArray(style)) {
- for (const name of style) {
- const cased = camelCase(name);
- ret[name] = this.node.style[cased];
- }
- return ret;
- } // get style for property
- if (typeof style === 'string') {
- return this.node.style[camelCase(style)];
- } // set styles in object
- if (typeof style === 'object') {
- for (const name in style) {
- // set empty string if null/undefined/'' was given
- this.node.style[camelCase(name)] = style[name] == null || isBlank.test(style[name]) ? '' : style[name];
- }
- }
- } // set style for property
- if (arguments.length === 2) {
- this.node.style[camelCase(style)] = val == null || isBlank.test(val) ? '' : val;
- }
- return this;
- } // Show element
- function show() {
- return this.css('display', '');
- } // Hide element
- function hide() {
- return this.css('display', 'none');
- } // Is element visible?
- function visible() {
- return this.css('display') !== 'none';
- }
- registerMethods('Dom', {
- css,
- show,
- hide,
- visible
- });
- function data(a, v, r) {
- if (a == null) {
- // get an object of attributes
- return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5)));
- } else if (a instanceof Array) {
- const data = {};
- for (const key of a) {
- data[key] = this.data(key);
- }
- return data;
- } else if (typeof a === 'object') {
- for (v in a) {
- this.data(v, a[v]);
- }
- } else if (arguments.length < 2) {
- try {
- return JSON.parse(this.attr('data-' + a));
- } catch (e) {
- return this.attr('data-' + a);
- }
- } else {
- this.attr('data-' + a, v === null ? null : r === true || typeof v === 'string' || typeof v === 'number' ? v : JSON.stringify(v));
- }
- return this;
- }
- registerMethods('Dom', {
- data
- });
- function remember(k, v) {
- // remember every item in an object individually
- if (typeof arguments[0] === 'object') {
- for (const key in k) {
- this.remember(key, k[key]);
- }
- } else if (arguments.length === 1) {
- // retrieve memory
- return this.memory()[k];
- } else {
- // store memory
- this.memory()[k] = v;
- }
- return this;
- } // Erase a given memory
- function forget() {
- if (arguments.length === 0) {
- this._memory = {};
- } else {
- for (let i = arguments.length - 1; i >= 0; i--) {
- delete this.memory()[arguments[i]];
- }
- }
- return this;
- } // This triggers creation of a new hidden class which is not performant
- // However, this function is not rarely used so it will not happen frequently
- // Return local memory object
- function memory() {
- return this._memory = this._memory || {};
- }
- registerMethods('Dom', {
- remember,
- forget,
- memory
- });
- function sixDigitHex(hex) {
- return hex.length === 4 ? ['#', hex.substring(1, 2), hex.substring(1, 2), hex.substring(2, 3), hex.substring(2, 3), hex.substring(3, 4), hex.substring(3, 4)].join('') : hex;
- }
- function componentHex(component) {
- const integer = Math.round(component);
- const bounded = Math.max(0, Math.min(255, integer));
- const hex = bounded.toString(16);
- return hex.length === 1 ? '0' + hex : hex;
- }
- function is(object, space) {
- for (let i = space.length; i--;) {
- if (object[space[i]] == null) {
- return false;
- }
- }
- return true;
- }
- function getParameters(a, b) {
- const params = is(a, 'rgb') ? {
- _a: a.r,
- _b: a.g,
- _c: a.b,
- _d: 0,
- space: 'rgb'
- } : is(a, 'xyz') ? {
- _a: a.x,
- _b: a.y,
- _c: a.z,
- _d: 0,
- space: 'xyz'
- } : is(a, 'hsl') ? {
- _a: a.h,
- _b: a.s,
- _c: a.l,
- _d: 0,
- space: 'hsl'
- } : is(a, 'lab') ? {
- _a: a.l,
- _b: a.a,
- _c: a.b,
- _d: 0,
- space: 'lab'
- } : is(a, 'lch') ? {
- _a: a.l,
- _b: a.c,
- _c: a.h,
- _d: 0,
- space: 'lch'
- } : is(a, 'cmyk') ? {
- _a: a.c,
- _b: a.m,
- _c: a.y,
- _d: a.k,
- space: 'cmyk'
- } : {
- _a: 0,
- _b: 0,
- _c: 0,
- space: 'rgb'
- };
- params.space = b || params.space;
- return params;
- }
- function cieSpace(space) {
- if (space === 'lab' || space === 'xyz' || space === 'lch') {
- return true;
- } else {
- return false;
- }
- }
- function hueToRgb(p, q, t) {
- if (t < 0) t += 1;
- if (t > 1) t -= 1;
- if (t < 1 / 6) return p + (q - p) * 6 * t;
- if (t < 1 / 2) return q;
- if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
- return p;
- }
- class Color {
- constructor(...inputs) {
- this.init(...inputs);
- } // Test if given value is a color
- static isColor(color) {
- return color && (color instanceof Color || this.isRgb(color) || this.test(color));
- } // Test if given value is an rgb object
- static isRgb(color) {
- return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number';
- }
- /*
- Generating random colors
- */
- static random(mode = 'vibrant', t, u) {
- // Get the math modules
- const {
- random,
- round,
- sin,
- PI: pi
- } = Math; // Run the correct generator
- if (mode === 'vibrant') {
- const l = (81 - 57) * random() + 57;
- const c = (83 - 45) * random() + 45;
- const h = 360 * random();
- const color = new Color(l, c, h, 'lch');
- return color;
- } else if (mode === 'sine') {
- t = t == null ? random() : t;
- const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150);
- const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200);
- const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150);
- const color = new Color(r, g, b);
- return color;
- } else if (mode === 'pastel') {
- const l = (94 - 86) * random() + 86;
- const c = (26 - 9) * random() + 9;
- const h = 360 * random();
- const color = new Color(l, c, h, 'lch');
- return color;
- } else if (mode === 'dark') {
- const l = 10 + 10 * random();
- const c = (125 - 75) * random() + 86;
- const h = 360 * random();
- const color = new Color(l, c, h, 'lch');
- return color;
- } else if (mode === 'rgb') {
- const r = 255 * random();
- const g = 255 * random();
- const b = 255 * random();
- const color = new Color(r, g, b);
- return color;
- } else if (mode === 'lab') {
- const l = 100 * random();
- const a = 256 * random() - 128;
- const b = 256 * random() - 128;
- const color = new Color(l, a, b, 'lab');
- return color;
- } else if (mode === 'grey') {
- const grey = 255 * random();
- const color = new Color(grey, grey, grey);
- return color;
- } else {
- throw new Error('Unsupported random color mode');
- }
- } // Test if given value is a color string
- static test(color) {
- return typeof color === 'string' && (isHex.test(color) || isRgb.test(color));
- }
- cmyk() {
- // Get the rgb values for the current color
- const {
- _a,
- _b,
- _c
- } = this.rgb();
- const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format
- const k = Math.min(1 - r, 1 - g, 1 - b);
- if (k === 1) {
- // Catch the black case
- return new Color(0, 0, 0, 1, 'cmyk');
- }
- const c = (1 - r - k) / (1 - k);
- const m = (1 - g - k) / (1 - k);
- const y = (1 - b - k) / (1 - k); // Construct the new color
- const color = new Color(c, m, y, k, 'cmyk');
- return color;
- }
- hsl() {
- // Get the rgb values
- const {
- _a,
- _b,
- _c
- } = this.rgb();
- const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const l = (max + min) / 2; // If the r, g, v values are identical then we are grey
- const isGrey = max === min; // Calculate the hue and saturation
- const delta = max - min;
- const s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
- const h = isGrey ? 0 : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / delta + 2) / 6 : max === b ? ((r - g) / delta + 4) / 6 : 0; // Construct and return the new color
- const color = new Color(360 * h, 100 * s, 100 * l, 'hsl');
- return color;
- }
- init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') {
- // This catches the case when a falsy value is passed like ''
- a = !a ? 0 : a; // Reset all values in case the init function is rerun with new color space
- if (this.space) {
- for (const component in this.space) {
- delete this[this.space[component]];
- }
- }
- if (typeof a === 'number') {
- // Allow for the case that we don't need d...
- space = typeof d === 'string' ? d : space;
- d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color
- Object.assign(this, {
- _a: a,
- _b: b,
- _c: c,
- _d: d,
- space
- }); // If the user gave us an array, make the color from it
- } else if (a instanceof Array) {
- this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb';
- Object.assign(this, {
- _a: a[0],
- _b: a[1],
- _c: a[2],
- _d: a[3] || 0
- });
- } else if (a instanceof Object) {
- // Set the object up and assign its values directly
- const values = getParameters(a, b);
- Object.assign(this, values);
- } else if (typeof a === 'string') {
- if (isRgb.test(a)) {
- const noWhitespace = a.replace(whitespace, '');
- const [_a, _b, _c] = rgb.exec(noWhitespace).slice(1, 4).map(v => parseInt(v));
- Object.assign(this, {
- _a,
- _b,
- _c,
- _d: 0,
- space: 'rgb'
- });
- } else if (isHex.test(a)) {
- const hexParse = v => parseInt(v, 16);
- const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse);
- Object.assign(this, {
- _a,
- _b,
- _c,
- _d: 0,
- space: 'rgb'
- });
- } else throw Error('Unsupported string format, can\'t construct Color');
- } // Now add the components as a convenience
- const {
- _a,
- _b,
- _c,
- _d
- } = this;
- const components = this.space === 'rgb' ? {
- r: _a,
- g: _b,
- b: _c
- } : this.space === 'xyz' ? {
- x: _a,
- y: _b,
- z: _c
- } : this.space === 'hsl' ? {
- h: _a,
- s: _b,
- l: _c
- } : this.space === 'lab' ? {
- l: _a,
- a: _b,
- b: _c
- } : this.space === 'lch' ? {
- l: _a,
- c: _b,
- h: _c
- } : this.space === 'cmyk' ? {
- c: _a,
- m: _b,
- y: _c,
- k: _d
- } : {};
- Object.assign(this, components);
- }
- lab() {
- // Get the xyz color
- const {
- x,
- y,
- z
- } = this.xyz(); // Get the lab components
- const l = 116 * y - 16;
- const a = 500 * (x - y);
- const b = 200 * (y - z); // Construct and return a new color
- const color = new Color(l, a, b, 'lab');
- return color;
- }
- lch() {
- // Get the lab color directly
- const {
- l,
- a,
- b
- } = this.lab(); // Get the chromaticity and the hue using polar coordinates
- const c = Math.sqrt(a ** 2 + b ** 2);
- let h = 180 * Math.atan2(b, a) / Math.PI;
- if (h < 0) {
- h *= -1;
- h = 360 - h;
- } // Make a new color and return it
- const color = new Color(l, c, h, 'lch');
- return color;
- }
- /*
- Conversion Methods
- */
- rgb() {
- if (this.space === 'rgb') {
- return this;
- } else if (cieSpace(this.space)) {
- // Convert to the xyz color space
- let {
- x,
- y,
- z
- } = this;
- if (this.space === 'lab' || this.space === 'lch') {
- // Get the values in the lab space
- let {
- l,
- a,
- b
- } = this;
- if (this.space === 'lch') {
- const {
- c,
- h
- } = this;
- const dToR = Math.PI / 180;
- a = c * Math.cos(dToR * h);
- b = c * Math.sin(dToR * h);
- } // Undo the nonlinear function
- const yL = (l + 16) / 116;
- const xL = a / 500 + yL;
- const zL = yL - b / 200; // Get the xyz values
- const ct = 16 / 116;
- const mx = 0.008856;
- const nm = 7.787;
- x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm);
- y = 1.00000 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm);
- z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm);
- } // Convert xyz to unbounded rgb values
- const rU = x * 3.2406 + y * -1.5372 + z * -0.4986;
- const gU = x * -0.9689 + y * 1.8758 + z * 0.0415;
- const bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values
- const pow = Math.pow;
- const bd = 0.0031308;
- const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU;
- const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU;
- const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color
- const color = new Color(255 * r, 255 * g, 255 * b);
- return color;
- } else if (this.space === 'hsl') {
- // https://bgrins.github.io/TinyColor/docs/tinycolor.html
- // Get the current hsl values
- let {
- h,
- s,
- l
- } = this;
- h /= 360;
- s /= 100;
- l /= 100; // If we are grey, then just make the color directly
- if (s === 0) {
- l *= 255;
- const color = new Color(l, l, l);
- return color;
- } // TODO I have no idea what this does :D If you figure it out, tell me!
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
- const p = 2 * l - q; // Get the rgb values
- const r = 255 * hueToRgb(p, q, h + 1 / 3);
- const g = 255 * hueToRgb(p, q, h);
- const b = 255 * hueToRgb(p, q, h - 1 / 3); // Make a new color
- const color = new Color(r, g, b);
- return color;
- } else if (this.space === 'cmyk') {
- // https://gist.github.com/felipesabino/5066336
- // Get the normalised cmyk values
- const {
- c,
- m,
- y,
- k
- } = this; // Get the rgb values
- const r = 255 * (1 - Math.min(1, c * (1 - k) + k));
- const g = 255 * (1 - Math.min(1, m * (1 - k) + k));
- const b = 255 * (1 - Math.min(1, y * (1 - k) + k)); // Form the color and return it
- const color = new Color(r, g, b);
- return color;
- } else {
- return this;
- }
- }
- toArray() {
- const {
- _a,
- _b,
- _c,
- _d,
- space
- } = this;
- return [_a, _b, _c, _d, space];
- }
- toHex() {
- const [r, g, b] = this._clamped().map(componentHex);
- return `#${r}${g}${b}`;
- }
- toRgb() {
- const [rV, gV, bV] = this._clamped();
- const string = `rgb(${rV},${gV},${bV})`;
- return string;
- }
- toString() {
- return this.toHex();
- }
- xyz() {
- // Normalise the red, green and blue values
- const {
- _a: r255,
- _b: g255,
- _c: b255
- } = this.rgb();
- const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space
- const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
- const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
- const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; // Convert to the xyz color space without bounding the values
- const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047;
- const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000;
- const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding
- const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116;
- const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116;
- const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color
- const color = new Color(x, y, z, 'xyz');
- return color;
- }
- /*
- Input and Output methods
- */
- _clamped() {
- const {
- _a,
- _b,
- _c
- } = this.rgb();
- const {
- max,
- min,
- round
- } = Math;
- const format = v => max(0, min(round(v), 255));
- return [_a, _b, _c].map(format);
- }
- /*
- Constructing colors
- */
- }
- class Point {
- // Initialize
- constructor(...args) {
- this.init(...args);
- } // Clone point
- clone() {
- return new Point(this);
- }
- init(x, y) {
- const base = {
- x: 0,
- y: 0
- }; // ensure source as object
- const source = Array.isArray(x) ? {
- x: x[0],
- y: x[1]
- } : typeof x === 'object' ? {
- x: x.x,
- y: x.y
- } : {
- x: x,
- y: y
- }; // merge source
- this.x = source.x == null ? base.x : source.x;
- this.y = source.y == null ? base.y : source.y;
- return this;
- }
- toArray() {
- return [this.x, this.y];
- }
- transform(m) {
- return this.clone().transformO(m);
- } // Transform point with matrix
- transformO(m) {
- if (!Matrix.isMatrixLike(m)) {
- m = new Matrix(m);
- }
- const {
- x,
- y
- } = this; // Perform the matrix multiplication
- this.x = m.a * x + m.c * y + m.e;
- this.y = m.b * x + m.d * y + m.f;
- return this;
- }
- }
- function point(x, y) {
- return new Point(x, y).transformO(this.screenCTM().inverseO());
- }
- function closeEnough(a, b, threshold) {
- return Math.abs(b - a) < (threshold || 1e-6);
- }
- class Matrix {
- constructor(...args) {
- this.init(...args);
- }
- static formatTransforms(o) {
- // Get all of the parameters required to form the matrix
- const flipBoth = o.flip === 'both' || o.flip === true;
- const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1;
- const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1;
- const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0;
- const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0;
- const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX;
- const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY;
- const shear = o.shear || 0;
- const theta = o.rotate || o.theta || 0;
- const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY);
- const ox = origin.x;
- const oy = origin.y; // We need Point to be invalid if nothing was passed because we cannot default to 0 here. That is why NaN
- const position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN);
- const px = position.x;
- const py = position.y;
- const translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY);
- const tx = translate.x;
- const ty = translate.y;
- const relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY);
- const rx = relative.x;
- const ry = relative.y; // Populate all of the values
- return {
- scaleX,
- scaleY,
- skewX,
- skewY,
- shear,
- theta,
- rx,
- ry,
- tx,
- ty,
- ox,
- oy,
- px,
- py
- };
- }
- static fromArray(a) {
- return {
- a: a[0],
- b: a[1],
- c: a[2],
- d: a[3],
- e: a[4],
- f: a[5]
- };
- }
- static isMatrixLike(o) {
- return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null;
- } // left matrix, right matrix, target matrix which is overwritten
- static matrixMultiply(l, r, o) {
- // Work out the product directly
- const a = l.a * r.a + l.c * r.b;
- const b = l.b * r.a + l.d * r.b;
- const c = l.a * r.c + l.c * r.d;
- const d = l.b * r.c + l.d * r.d;
- const e = l.e + l.a * r.e + l.c * r.f;
- const f = l.f + l.b * r.e + l.d * r.f; // make sure to use local variables because l/r and o could be the same
- o.a = a;
- o.b = b;
- o.c = c;
- o.d = d;
- o.e = e;
- o.f = f;
- return o;
- }
- around(cx, cy, matrix) {
- return this.clone().aroundO(cx, cy, matrix);
- } // Transform around a center point
- aroundO(cx, cy, matrix) {
- const dx = cx || 0;
- const dy = cy || 0;
- return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy);
- } // Clones this matrix
- clone() {
- return new Matrix(this);
- } // Decomposes this matrix into its affine parameters
- decompose(cx = 0, cy = 0) {
- // Get the parameters from the matrix
- const a = this.a;
- const b = this.b;
- const c = this.c;
- const d = this.d;
- const e = this.e;
- const f = this.f; // Figure out if the winding direction is clockwise or counterclockwise
- const determinant = a * d - b * c;
- const ccw = determinant > 0 ? 1 : -1; // Since we only shear in x, we can use the x basis to get the x scale
- // and the rotation of the resulting matrix
- const sx = ccw * Math.sqrt(a * a + b * b);
- const thetaRad = Math.atan2(ccw * b, ccw * a);
- const theta = 180 / Math.PI * thetaRad;
- const ct = Math.cos(thetaRad);
- const st = Math.sin(thetaRad); // We can then solve the y basis vector simultaneously to get the other
- // two affine parameters directly from these parameters
- const lam = (a * c + b * d) / determinant;
- const sy = c * sx / (lam * a - b) || d * sx / (lam * b + a); // Use the translations
- const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy);
- const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy); // Construct the decomposition and return it
- return {
- // Return the affine parameters
- scaleX: sx,
- scaleY: sy,
- shear: lam,
- rotate: theta,
- translateX: tx,
- translateY: ty,
- originX: cx,
- originY: cy,
- // Return the matrix parameters
- a: this.a,
- b: this.b,
- c: this.c,
- d: this.d,
- e: this.e,
- f: this.f
- };
- } // Check if two matrices are equal
- equals(other) {
- if (other === this) return true;
- const comp = new Matrix(other);
- return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f);
- } // Flip matrix on x or y, at a given offset
- flip(axis, around) {
- return this.clone().flipO(axis, around);
- }
- flipO(axis, around) {
- return axis === 'x' ? this.scaleO(-1, 1, around, 0) : axis === 'y' ? this.scaleO(1, -1, 0, around) : this.scaleO(-1, -1, axis, around || axis); // Define an x, y flip point
- } // Initialize
- init(source) {
- const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]); // ensure source as object
- source = source instanceof Element ? source.matrixify() : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) : Array.isArray(source) ? Matrix.fromArray(source) : typeof source === 'object' && Matrix.isMatrixLike(source) ? source : typeof source === 'object' ? new Matrix().transform(source) : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) : base; // Merge the source matrix with the base matrix
- this.a = source.a != null ? source.a : base.a;
- this.b = source.b != null ? source.b : base.b;
- this.c = source.c != null ? source.c : base.c;
- this.d = source.d != null ? source.d : base.d;
- this.e = source.e != null ? source.e : base.e;
- this.f = source.f != null ? source.f : base.f;
- return this;
- }
- inverse() {
- return this.clone().inverseO();
- } // Inverses matrix
- inverseO() {
- // Get the current parameters out of the matrix
- const a = this.a;
- const b = this.b;
- const c = this.c;
- const d = this.d;
- const e = this.e;
- const f = this.f; // Invert the 2x2 matrix in the top left
- const det = a * d - b * c;
- if (!det) throw new Error('Cannot invert ' + this); // Calculate the top 2x2 matrix
- const na = d / det;
- const nb = -b / det;
- const nc = -c / det;
- const nd = a / det; // Apply the inverted matrix to the top right
- const ne = -(na * e + nc * f);
- const nf = -(nb * e + nd * f); // Construct the inverted matrix
- this.a = na;
- this.b = nb;
- this.c = nc;
- this.d = nd;
- this.e = ne;
- this.f = nf;
- return this;
- }
- lmultiply(matrix) {
- return this.clone().lmultiplyO(matrix);
- }
- lmultiplyO(matrix) {
- const r = this;
- const l = matrix instanceof Matrix ? matrix : new Matrix(matrix);
- return Matrix.matrixMultiply(l, r, this);
- } // Left multiplies by the given matrix
- multiply(matrix) {
- return this.clone().multiplyO(matrix);
- }
- multiplyO(matrix) {
- // Get the matrices
- const l = this;
- const r = matrix instanceof Matrix ? matrix : new Matrix(matrix);
- return Matrix.matrixMultiply(l, r, this);
- } // Rotate matrix
- rotate(r, cx, cy) {
- return this.clone().rotateO(r, cx, cy);
- }
- rotateO(r, cx = 0, cy = 0) {
- // Convert degrees to radians
- r = radians(r);
- const cos = Math.cos(r);
- const sin = Math.sin(r);
- const {
- a,
- b,
- c,
- d,
- e,
- f
- } = this;
- this.a = a * cos - b * sin;
- this.b = b * cos + a * sin;
- this.c = c * cos - d * sin;
- this.d = d * cos + c * sin;
- this.e = e * cos - f * sin + cy * sin - cx * cos + cx;
- this.f = f * cos + e * sin - cx * sin - cy * cos + cy;
- return this;
- } // Scale matrix
- scale(x, y, cx, cy) {
- return this.clone().scaleO(...arguments);
- }
- scaleO(x, y = x, cx = 0, cy = 0) {
- // Support uniform scaling
- if (arguments.length === 3) {
- cy = cx;
- cx = y;
- y = x;
- }
- const {
- a,
- b,
- c,
- d,
- e,
- f
- } = this;
- this.a = a * x;
- this.b = b * y;
- this.c = c * x;
- this.d = d * y;
- this.e = e * x - cx * x + cx;
- this.f = f * y - cy * y + cy;
- return this;
- } // Shear matrix
- shear(a, cx, cy) {
- return this.clone().shearO(a, cx, cy);
- }
- shearO(lx, cx = 0, cy = 0) {
- const {
- a,
- b,
- c,
- d,
- e,
- f
- } = this;
- this.a = a + b * lx;
- this.c = c + d * lx;
- this.e = e + f * lx - cy * lx;
- return this;
- } // Skew Matrix
- skew(x, y, cx, cy) {
- return this.clone().skewO(...arguments);
- }
- skewO(x, y = x, cx = 0, cy = 0) {
- // support uniformal skew
- if (arguments.length === 3) {
- cy = cx;
- cx = y;
- y = x;
- } // Convert degrees to radians
- x = radians(x);
- y = radians(y);
- const lx = Math.tan(x);
- const ly = Math.tan(y);
- const {
- a,
- b,
- c,
- d,
- e,
- f
- } = this;
- this.a = a + b * lx;
- this.b = b + a * ly;
- this.c = c + d * lx;
- this.d = d + c * ly;
- this.e = e + f * lx - cy * lx;
- this.f = f + e * ly - cx * ly;
- return this;
- } // SkewX
- skewX(x, cx, cy) {
- return this.skew(x, 0, cx, cy);
- } // SkewY
- skewY(y, cx, cy) {
- return this.skew(0, y, cx, cy);
- }
- toArray() {
- return [this.a, this.b, this.c, this.d, this.e, this.f];
- } // Convert matrix to string
- toString() {
- return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')';
- } // Transform a matrix into another matrix by manipulating the space
- transform(o) {
- // Check if o is a matrix and then left multiply it directly
- if (Matrix.isMatrixLike(o)) {
- const matrix = new Matrix(o);
- return matrix.multiplyO(this);
- } // Get the proposed transformations and the current transformations
- const t = Matrix.formatTransforms(o);
- const current = this;
- const {
- x: ox,
- y: oy
- } = new Point(t.ox, t.oy).transform(current); // Construct the resulting matrix
- const transformer = new Matrix().translateO(t.rx, t.ry).lmultiplyO(current).translateO(-ox, -oy).scaleO(t.scaleX, t.scaleY).skewO(t.skewX, t.skewY).shearO(t.shear).rotateO(t.theta).translateO(ox, oy); // If we want the origin at a particular place, we force it there
- if (isFinite(t.px) || isFinite(t.py)) {
- const origin = new Point(ox, oy).transform(transformer); // TODO: Replace t.px with isFinite(t.px)
- // Doesn't work because t.px is also 0 if it wasn't passed
- const dx = isFinite(t.px) ? t.px - origin.x : 0;
- const dy = isFinite(t.py) ? t.py - origin.y : 0;
- transformer.translateO(dx, dy);
- } // Translate now after positioning
- transformer.translateO(t.tx, t.ty);
- return transformer;
- } // Translate matrix
- translate(x, y) {
- return this.clone().translateO(x, y);
- }
- translateO(x, y) {
- this.e += x || 0;
- this.f += y || 0;
- return this;
- }
- valueOf() {
- return {
- a: this.a,
- b: this.b,
- c: this.c,
- d: this.d,
- e: this.e,
- f: this.f
- };
- }
- }
- function ctm() {
- return new Matrix(this.node.getCTM());
- }
- function screenCTM() {
- /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
- This is needed because FF does not return the transformation matrix
- for the inner coordinate system when getScreenCTM() is called on nested svgs.
- However all other Browsers do that */
- if (typeof this.isRoot === 'function' && !this.isRoot()) {
- const rect = this.rect(1, 1);
- const m = rect.node.getScreenCTM();
- rect.remove();
- return new Matrix(m);
- }
- return new Matrix(this.node.getScreenCTM());
- }
- register(Matrix, 'Matrix');
- function parser() {
- // Reuse cached element if possible
- if (!parser.nodes) {
- const svg = makeInstance().size(2, 0);
- svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';');
- svg.attr('focusable', 'false');
- svg.attr('aria-hidden', 'true');
- const path = svg.path().node;
- parser.nodes = {
- svg,
- path
- };
- }
- if (!parser.nodes.svg.node.parentNode) {
- const b = globals.document.body || globals.document.documentElement;
- parser.nodes.svg.addTo(b);
- }
- return parser.nodes;
- }
- function isNulledBox(box) {
- return !box.width && !box.height && !box.x && !box.y;
- }
- function domContains(node) {
- return node === globals.document || (globals.document.documentElement.contains || function (node) {
- // This is IE - it does not support contains() for top-level SVGs
- while (node.parentNode) {
- node = node.parentNode;
- }
- return node === globals.document;
- }).call(globals.document.documentElement, node);
- }
- class Box {
- constructor(...args) {
- this.init(...args);
- }
- addOffset() {
- // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
- this.x += globals.window.pageXOffset;
- this.y += globals.window.pageYOffset;
- return new Box(this);
- }
- init(source) {
- const base = [0, 0, 0, 0];
- source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source : typeof source === 'object' ? [source.left != null ? source.left : source.x, source.top != null ? source.top : source.y, source.width, source.height] : arguments.length === 4 ? [].slice.call(arguments) : base;
- this.x = source[0] || 0;
- this.y = source[1] || 0;
- this.width = this.w = source[2] || 0;
- this.height = this.h = source[3] || 0; // Add more bounding box properties
- this.x2 = this.x + this.w;
- this.y2 = this.y + this.h;
- this.cx = this.x + this.w / 2;
- this.cy = this.y + this.h / 2;
- return this;
- }
- isNulled() {
- return isNulledBox(this);
- } // Merge rect box with another, return a new instance
- merge(box) {
- const x = Math.min(this.x, box.x);
- const y = Math.min(this.y, box.y);
- const width = Math.max(this.x + this.width, box.x + box.width) - x;
- const height = Math.max(this.y + this.height, box.y + box.height) - y;
- return new Box(x, y, width, height);
- }
- toArray() {
- return [this.x, this.y, this.width, this.height];
- }
- toString() {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
- }
- transform(m) {
- if (!(m instanceof Matrix)) {
- m = new Matrix(m);
- }
- let xMin = Infinity;
- let xMax = -Infinity;
- let yMin = Infinity;
- let yMax = -Infinity;
- const pts = [new Point(this.x, this.y), new Point(this.x2, this.y), new Point(this.x, this.y2), new Point(this.x2, this.y2)];
- pts.forEach(function (p) {
- p = p.transform(m);
- xMin = Math.min(xMin, p.x);
- xMax = Math.max(xMax, p.x);
- yMin = Math.min(yMin, p.y);
- yMax = Math.max(yMax, p.y);
- });
- return new Box(xMin, yMin, xMax - xMin, yMax - yMin);
- }
- }
- function getBox(el, getBBoxFn, retry) {
- let box;
- try {
- // Try to get the box with the provided function
- box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry
- // by throwing an error here...
- if (isNulledBox(box) && !domContains(el.node)) {
- throw new Error('Element not in the dom');
- }
- } catch (e) {
- // ... and calling the retry handler here
- box = retry(el);
- }
- return box;
- }
- function bbox() {
- // Function to get bbox is getBBox()
- const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element
- // so we can get the bbox from it when we try again
- const retry = el => {
- try {
- const clone = el.clone().addTo(parser().svg).show();
- const box = clone.node.getBBox();
- clone.remove();
- return box;
- } catch (e) {
- // We give up...
- throw new Error(`Getting bbox of element "${el.node.nodeName}" is not possible: ${e.toString()}`);
- }
- };
- const box = getBox(this, getBBox, retry);
- const bbox = new Box(box);
- return bbox;
- }
- function rbox(el) {
- const getRBox = node => node.getBoundingClientRect();
- const retry = el => {
- // There is no point in trying tricks here because if we insert the element into the dom ourselves
- // it obviously will be at the wrong position
- throw new Error(`Getting rbox of element "${el.node.nodeName}" is not possible`);
- };
- const box = getBox(this, getRBox, retry);
- const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element
- if (el) {
- return rbox.transform(el.screenCTM().inverseO());
- } // Else we want it in absolute screen coordinates
- // Therefore we need to add the scrollOffset
- return rbox.addOffset();
- } // Checks whether the given point is inside the bounding box
- function inside(x, y) {
- const box = this.bbox();
- return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height;
- }
- registerMethods({
- viewbox: {
- viewbox(x, y, width, height) {
- // act as getter
- if (x == null) return new Box(this.attr('viewBox')); // act as setter
- return this.attr('viewBox', new Box(x, y, width, height));
- },
- zoom(level, point) {
- // Its best to rely on the attributes here and here is why:
- // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)
- // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely
- // that means, their clientRect is always as big as the content.
- // Furthermore this size is incorrect if the element is further transformed by its parents
- // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!
- // getBBox: returns the bounding box of its content - that doesn't help!
- let {
- width,
- height
- } = this.attr(['width', 'height']); // Width and height is a string when a number with a unit is present which we can't use
- // So we try clientXYZ
- if (!width && !height || typeof width === 'string' || typeof height === 'string') {
- width = this.node.clientWidth;
- height = this.node.clientHeight;
- } // Giving up...
- if (!width || !height) {
- throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element');
- }
- const v = this.viewbox();
- const zoomX = width / v.width;
- const zoomY = height / v.height;
- const zoom = Math.min(zoomX, zoomY);
- if (level == null) {
- return zoom;
- }
- let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from
- // The * 100 is a bit of wiggle room for the matrix transformation
- if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100;
- point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y);
- const box = new Box(v).transform(new Matrix({
- scale: zoomAmount,
- origin: point
- }));
- return this.viewbox(box);
- }
- }
- });
- register(Box, 'Box');
- class List extends Array {
- constructor(arr = [], ...args) {
- super(arr, ...args);
- if (typeof arr === 'number') return this;
- this.length = 0;
- this.push(...arr);
- }
- }
- extend([List], {
- each(fnOrMethodName, ...args) {
- if (typeof fnOrMethodName === 'function') {
- return this.map((el, i, arr) => {
- return fnOrMethodName.call(el, el, i, arr);
- });
- } else {
- return this.map(el => {
- return el[fnOrMethodName](...args);
- });
- }
- },
- toArray() {
- return Array.prototype.concat.apply([], this);
- }
- });
- const reserved = ['toArray', 'constructor', 'each'];
- List.extend = function (methods) {
- methods = methods.reduce((obj, name) => {
- // Don't overwrite own methods
- if (reserved.includes(name)) return obj; // Don't add private methods
- if (name[0] === '_') return obj; // Relay every call to each()
- obj[name] = function (...attrs) {
- return this.each(name, ...attrs);
- };
- return obj;
- }, {});
- extend([List], methods);
- };
- function baseFind(query, parent) {
- return new List(map((parent || globals.document).querySelectorAll(query), function (node) {
- return adopt(node);
- }));
- } // Scoped find method
- function find(query) {
- return baseFind(query, this.node);
- }
- function findOne(query) {
- return adopt(this.node.querySelector(query));
- }
- let listenerId = 0;
- const windowEvents = {};
- function getEvents(instance) {
- let n = instance.getEventHolder(); // We dont want to save events in global space
- if (n === globals.window) n = windowEvents;
- if (!n.events) n.events = {};
- return n.events;
- }
- function getEventTarget(instance) {
- return instance.getEventTarget();
- }
- function clearEvents(instance) {
- let n = instance.getEventHolder();
- if (n === globals.window) n = windowEvents;
- if (n.events) n.events = {};
- } // Add event binder in the SVG namespace
- function on(node, events, listener, binding, options) {
- const l = listener.bind(binding || node);
- const instance = makeInstance(node);
- const bag = getEvents(instance);
- const n = getEventTarget(instance); // events can be an array of events or a string of events
- events = Array.isArray(events) ? events : events.split(delimiter); // add id to listener
- if (!listener._svgjsListenerId) {
- listener._svgjsListenerId = ++listenerId;
- }
- events.forEach(function (event) {
- const ev = event.split('.')[0];
- const ns = event.split('.')[1] || '*'; // ensure valid object
- bag[ev] = bag[ev] || {};
- bag[ev][ns] = bag[ev][ns] || {}; // reference listener
- bag[ev][ns][listener._svgjsListenerId] = l; // add listener
- n.addEventListener(ev, l, options || false);
- });
- } // Add event unbinder in the SVG namespace
- function off(node, events, listener, options) {
- const instance = makeInstance(node);
- const bag = getEvents(instance);
- const n = getEventTarget(instance); // listener can be a function or a number
- if (typeof listener === 'function') {
- listener = listener._svgjsListenerId;
- if (!listener) return;
- } // events can be an array of events or a string or undefined
- events = Array.isArray(events) ? events : (events || '').split(delimiter);
- events.forEach(function (event) {
- const ev = event && event.split('.')[0];
- const ns = event && event.split('.')[1];
- let namespace, l;
- if (listener) {
- // remove listener reference
- if (bag[ev] && bag[ev][ns || '*']) {
- // removeListener
- n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false);
- delete bag[ev][ns || '*'][listener];
- }
- } else if (ev && ns) {
- // remove all listeners for a namespaced event
- if (bag[ev] && bag[ev][ns]) {
- for (l in bag[ev][ns]) {
- off(n, [ev, ns].join('.'), l);
- }
- delete bag[ev][ns];
- }
- } else if (ns) {
- // remove all listeners for a specific namespace
- for (event in bag) {
- for (namespace in bag[event]) {
- if (ns === namespace) {
- off(n, [event, ns].join('.'));
- }
- }
- }
- } else if (ev) {
- // remove all listeners for the event
- if (bag[ev]) {
- for (namespace in bag[ev]) {
- off(n, [ev, namespace].join('.'));
- }
- delete bag[ev];
- }
- } else {
- // remove all listeners on a given node
- for (event in bag) {
- off(n, event);
- }
- clearEvents(instance);
- }
- });
- }
- function dispatch(node, event, data, options) {
- const n = getEventTarget(node); // Dispatch event
- if (event instanceof globals.window.Event) {
- n.dispatchEvent(event);
- } else {
- event = new globals.window.CustomEvent(event, {
- detail: data,
- cancelable: true,
- ...options
- });
- n.dispatchEvent(event);
- }
- return event;
- }
- class EventTarget extends Base {
- addEventListener() {}
- dispatch(event, data, options) {
- return dispatch(this, event, data, options);
- }
- dispatchEvent(event) {
- const bag = this.getEventHolder().events;
- if (!bag) return true;
- const events = bag[event.type];
- for (const i in events) {
- for (const j in events[i]) {
- events[i][j](event);
- }
- }
- return !event.defaultPrevented;
- } // Fire given event
- fire(event, data, options) {
- this.dispatch(event, data, options);
- return this;
- }
- getEventHolder() {
- return this;
- }
- getEventTarget() {
- return this;
- } // Unbind event from listener
- off(event, listener, options) {
- off(this, event, listener, options);
- return this;
- } // Bind given event to listener
- on(event, listener, binding, options) {
- on(this, event, listener, binding, options);
- return this;
- }
- removeEventListener() {}
- }
- register(EventTarget, 'EventTarget');
- function noop() {} // Default animation values
- const timeline = {
- duration: 400,
- ease: '>',
- delay: 0
- }; // Default attribute values
- const attrs = {
- // fill and stroke
- 'fill-opacity': 1,
- 'stroke-opacity': 1,
- 'stroke-width': 0,
- 'stroke-linejoin': 'miter',
- 'stroke-linecap': 'butt',
- fill: '#000000',
- stroke: '#000000',
- opacity: 1,
- // position
- x: 0,
- y: 0,
- cx: 0,
- cy: 0,
- // size
- width: 0,
- height: 0,
- // radius
- r: 0,
- rx: 0,
- ry: 0,
- // gradient
- offset: 0,
- 'stop-opacity': 1,
- 'stop-color': '#000000',
- // text
- 'text-anchor': 'start'
- };
- var defaults = {
- __proto__: null,
- noop: noop,
- timeline: timeline,
- attrs: attrs
- };
- class SVGArray extends Array {
- constructor(...args) {
- super(...args);
- this.init(...args);
- }
- clone() {
- return new this.constructor(this);
- }
- init(arr) {
- // This catches the case, that native map tries to create an array with new Array(1)
- if (typeof arr === 'number') return this;
- this.length = 0;
- this.push(...this.parse(arr));
- return this;
- } // Parse whitespace separated string
- parse(array = []) {
- // If already is an array, no need to parse it
- if (array instanceof Array) return array;
- return array.trim().split(delimiter).map(parseFloat);
- }
- toArray() {
- return Array.prototype.concat.apply([], this);
- }
- toSet() {
- return new Set(this);
- }
- toString() {
- return this.join(' ');
- } // Flattens the array if needed
- valueOf() {
- const ret = [];
- ret.push(...this);
- return ret;
- }
- }
- class SVGNumber {
- // Initialize
- constructor(...args) {
- this.init(...args);
- }
- convert(unit) {
- return new SVGNumber(this.value, unit);
- } // Divide number
- divide(number) {
- number = new SVGNumber(number);
- return new SVGNumber(this / number, this.unit || number.unit);
- }
- init(value, unit) {
- unit = Array.isArray(value) ? value[1] : unit;
- value = Array.isArray(value) ? value[0] : value; // initialize defaults
- this.value = 0;
- this.unit = unit || ''; // parse value
- if (typeof value === 'number') {
- // ensure a valid numeric value
- this.value = isNaN(value) ? 0 : !isFinite(value) ? value < 0 ? -3.4e+38 : +3.4e+38 : value;
- } else if (typeof value === 'string') {
- unit = value.match(numberAndUnit);
- if (unit) {
- // make value numeric
- this.value = parseFloat(unit[1]); // normalize
- if (unit[5] === '%') {
- this.value /= 100;
- } else if (unit[5] === 's') {
- this.value *= 1000;
- } // store unit
- this.unit = unit[5];
- }
- } else {
- if (value instanceof SVGNumber) {
- this.value = value.valueOf();
- this.unit = value.unit;
- }
- }
- return this;
- } // Subtract number
- minus(number) {
- number = new SVGNumber(number);
- return new SVGNumber(this - number, this.unit || number.unit);
- } // Add number
- plus(number) {
- number = new SVGNumber(number);
- return new SVGNumber(this + number, this.unit || number.unit);
- } // Multiply number
- times(number) {
- number = new SVGNumber(number);
- return new SVGNumber(this * number, this.unit || number.unit);
- }
- toArray() {
- return [this.value, this.unit];
- }
- toJSON() {
- return this.toString();
- }
- toString() {
- return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit;
- }
- valueOf() {
- return this.value;
- }
- }
- const hooks = [];
- function registerAttrHook(fn) {
- hooks.push(fn);
- } // Set svg element attribute
- function attr(attr, val, ns) {
- // act as full getter
- if (attr == null) {
- // get an object of attributes
- attr = {};
- val = this.node.attributes;
- for (const node of val) {
- attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue;
- }
- return attr;
- } else if (attr instanceof Array) {
- // loop through array and get all values
- return attr.reduce((last, curr) => {
- last[curr] = this.attr(curr);
- return last;
- }, {});
- } else if (typeof attr === 'object' && attr.constructor === Object) {
- // apply every attribute individually if an object is passed
- for (val in attr) this.attr(val, attr[val]);
- } else if (val === null) {
- // remove value
- this.node.removeAttribute(attr);
- } else if (val == null) {
- // act as a getter if the first and only argument is not an object
- val = this.node.getAttribute(attr);
- return val == null ? attrs[attr] : isNumber.test(val) ? parseFloat(val) : val;
- } else {
- // Loop through hooks and execute them to convert value
- val = hooks.reduce((_val, hook) => {
- return hook(attr, _val, this);
- }, val); // ensure correct numeric values (also accepts NaN and Infinity)
- if (typeof val === 'number') {
- val = new SVGNumber(val);
- } else if (Color.isColor(val)) {
- // ensure full hex color
- val = new Color(val);
- } else if (val.constructor === Array) {
- // Check for plain arrays and parse array values
- val = new SVGArray(val);
- } // if the passed attribute is leading...
- if (attr === 'leading') {
- // ... call the leading method instead
- if (this.leading) {
- this.leading(val);
- }
- } else {
- // set given attribute on node
- typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString()) : this.node.setAttribute(attr, val.toString());
- } // rebuild if required
- if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
- this.rebuild();
- }
- }
- return this;
- }
- class Dom extends EventTarget {
- constructor(node, attrs) {
- super();
- this.node = node;
- this.type = node.nodeName;
- if (attrs && node !== attrs) {
- this.attr(attrs);
- }
- } // Add given element at a position
- add(element, i) {
- element = makeInstance(element); // If non-root svg nodes are added we have to remove their namespaces
- if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {
- element.removeNamespace();
- }
- if (i == null) {
- this.node.appendChild(element.node);
- } else if (element.node !== this.node.childNodes[i]) {
- this.node.insertBefore(element.node, this.node.childNodes[i]);
- }
- return this;
- } // Add element to given container and return self
- addTo(parent, i) {
- return makeInstance(parent).put(this, i);
- } // Returns all child elements
- children() {
- return new List(map(this.node.children, function (node) {
- return adopt(node);
- }));
- } // Remove all elements in this container
- clear() {
- // remove children
- while (this.node.hasChildNodes()) {
- this.node.removeChild(this.node.lastChild);
- }
- return this;
- } // Clone element
- clone(deep = true, assignNewIds = true) {
- // write dom data to the dom so the clone can pickup the data
- this.writeDataToDom(); // clone element
- let nodeClone = this.node.cloneNode(deep);
- if (assignNewIds) {
- // assign new id
- nodeClone = assignNewId(nodeClone);
- }
- return new this.constructor(nodeClone);
- } // Iterates over all children and invokes a given block
- each(block, deep) {
- const children = this.children();
- let i, il;
- for (i = 0, il = children.length; i < il; i++) {
- block.apply(children[i], [i, children]);
- if (deep) {
- children[i].each(block, deep);
- }
- }
- return this;
- }
- element(nodeName, attrs) {
- return this.put(new Dom(create(nodeName), attrs));
- } // Get first child
- first() {
- return adopt(this.node.firstChild);
- } // Get a element at the given index
- get(i) {
- return adopt(this.node.childNodes[i]);
- }
- getEventHolder() {
- return this.node;
- }
- getEventTarget() {
- return this.node;
- } // Checks if the given element is a child
- has(element) {
- return this.index(element) >= 0;
- }
- html(htmlOrFn, outerHTML) {
- return this.xml(htmlOrFn, outerHTML, html);
- } // Get / set id
- id(id) {
- // generate new id if no id set
- if (typeof id === 'undefined' && !this.node.id) {
- this.node.id = eid(this.type);
- } // don't set directly with this.node.id to make `null` work correctly
- return this.attr('id', id);
- } // Gets index of given element
- index(element) {
- return [].slice.call(this.node.childNodes).indexOf(element.node);
- } // Get the last child
- last() {
- return adopt(this.node.lastChild);
- } // matches the element vs a css selector
- matches(selector) {
- const el = this.node;
- const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null;
- return matcher && matcher.call(el, selector);
- } // Returns the parent element instance
- parent(type) {
- let parent = this; // check for parent
- if (!parent.node.parentNode) return null; // get parent element
- parent = adopt(parent.node.parentNode);
- if (!type) return parent; // loop through ancestors if type is given
- do {
- if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent;
- } while (parent = adopt(parent.node.parentNode));
- return parent;
- } // Basically does the same as `add()` but returns the added element instead
- put(element, i) {
- element = makeInstance(element);
- this.add(element, i);
- return element;
- } // Add element to given container and return container
- putIn(parent, i) {
- return makeInstance(parent).add(this, i);
- } // Remove element
- remove() {
- if (this.parent()) {
- this.parent().removeElement(this);
- }
- return this;
- } // Remove a given child
- removeElement(element) {
- this.node.removeChild(element.node);
- return this;
- } // Replace this with element
- replace(element) {
- element = makeInstance(element);
- if (this.node.parentNode) {
- this.node.parentNode.replaceChild(element.node, this.node);
- }
- return element;
- }
- round(precision = 2, map = null) {
- const factor = 10 ** precision;
- const attrs = this.attr(map);
- for (const i in attrs) {
- if (typeof attrs[i] === 'number') {
- attrs[i] = Math.round(attrs[i] * factor) / factor;
- }
- }
- this.attr(attrs);
- return this;
- } // Import / Export raw svg
- svg(svgOrFn, outerSVG) {
- return this.xml(svgOrFn, outerSVG, svg);
- } // Return id on string conversion
- toString() {
- return this.id();
- }
- words(text) {
- // This is faster than removing all children and adding a new one
- this.node.textContent = text;
- return this;
- }
- wrap(node) {
- const parent = this.parent();
- if (!parent) {
- return this.addTo(node);
- }
- const position = parent.index(this);
- return parent.put(node, position).put(this);
- } // write svgjs data to the dom
- writeDataToDom() {
- // dump variables recursively
- this.each(function () {
- this.writeDataToDom();
- });
- return this;
- } // Import / Export raw svg
- xml(xmlOrFn, outerXML, ns) {
- if (typeof xmlOrFn === 'boolean') {
- ns = outerXML;
- outerXML = xmlOrFn;
- xmlOrFn = null;
- } // act as getter if no svg string is given
- if (xmlOrFn == null || typeof xmlOrFn === 'function') {
- // The default for exports is, that the outerNode is included
- outerXML = outerXML == null ? true : outerXML; // write svgjs data to the dom
- this.writeDataToDom();
- let current = this; // An export modifier was passed
- if (xmlOrFn != null) {
- current = adopt(current.node.cloneNode(true)); // If the user wants outerHTML we need to process this node, too
- if (outerXML) {
- const result = xmlOrFn(current);
- current = result || current; // The user does not want this node? Well, then he gets nothing
- if (result === false) return '';
- } // Deep loop through all children and apply modifier
- current.each(function () {
- const result = xmlOrFn(this);
- const _this = result || this; // If modifier returns false, discard node
- if (result === false) {
- this.remove(); // If modifier returns new node, use it
- } else if (result && this !== _this) {
- this.replace(_this);
- }
- }, true);
- } // Return outer or inner content
- return outerXML ? current.node.outerHTML : current.node.innerHTML;
- } // Act as setter if we got a string
- // The default for import is, that the current node is not replaced
- outerXML = outerXML == null ? false : outerXML; // Create temporary holder
- const well = create('wrapper', ns);
- const fragment = globals.document.createDocumentFragment(); // Dump raw svg
- well.innerHTML = xmlOrFn; // Transplant nodes into the fragment
- for (let len = well.children.length; len--;) {
- fragment.appendChild(well.firstElementChild);
- }
- const parent = this.parent(); // Add the whole fragment at once
- return outerXML ? this.replace(fragment) && parent : this.add(fragment);
- }
- }
- extend(Dom, {
- attr,
- find,
- findOne
- });
- register(Dom, 'Dom');
- class Element extends Dom {
- constructor(node, attrs) {
- super(node, attrs); // initialize data object
- this.dom = {}; // create circular reference
- this.node.instance = this;
- if (node.hasAttribute('svgjs:data')) {
- // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
- this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {});
- }
- } // Move element by its center
- center(x, y) {
- return this.cx(x).cy(y);
- } // Move by center over x-axis
- cx(x) {
- return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2);
- } // Move by center over y-axis
- cy(y) {
- return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2);
- } // Get defs
- defs() {
- const root = this.root();
- return root && root.defs();
- } // Relative move over x and y axes
- dmove(x, y) {
- return this.dx(x).dy(y);
- } // Relative move over x axis
- dx(x = 0) {
- return this.x(new SVGNumber(x).plus(this.x()));
- } // Relative move over y axis
- dy(y = 0) {
- return this.y(new SVGNumber(y).plus(this.y()));
- }
- getEventHolder() {
- return this;
- } // Set height of element
- height(height) {
- return this.attr('height', height);
- } // Move element to given x and y values
- move(x, y) {
- return this.x(x).y(y);
- } // return array of all ancestors of given type up to the root svg
- parents(until = this.root()) {
- const isSelector = typeof until === 'string';
- if (!isSelector) {
- until = makeInstance(until);
- }
- const parents = new List();
- let parent = this;
- while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') {
- parents.push(parent);
- if (!isSelector && parent.node === until.node) {
- break;
- }
- if (isSelector && parent.matches(until)) {
- break;
- }
- if (parent.node === this.root().node) {
- // We worked our way to the root and didn't match `until`
- return null;
- }
- }
- return parents;
- } // Get referenced element form attribute value
- reference(attr) {
- attr = this.attr(attr);
- if (!attr) return null;
- const m = (attr + '').match(reference);
- return m ? makeInstance(m[1]) : null;
- } // Get parent document
- root() {
- const p = this.parent(getClass(root));
- return p && p.root();
- } // set given data to the elements data property
- setData(o) {
- this.dom = o;
- return this;
- } // Set element size to given width and height
- size(width, height) {
- const p = proportionalSize(this, width, height);
- return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height));
- } // Set width of element
- width(width) {
- return this.attr('width', width);
- } // write svgjs data to the dom
- writeDataToDom() {
- // remove previously set data
- this.node.removeAttribute('svgjs:data');
- if (Object.keys(this.dom).length) {
- this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)); // see #428
- }
- return super.writeDataToDom();
- } // Move over x-axis
- x(x) {
- return this.attr('x', x);
- } // Move over y-axis
- y(y) {
- return this.attr('y', y);
- }
- }
- extend(Element, {
- bbox,
- rbox,
- inside,
- point,
- ctm,
- screenCTM
- });
- register(Element, 'Element');
- const sugar = {
- stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
- fill: ['color', 'opacity', 'rule'],
- prefix: function (t, a) {
- return a === 'color' ? t : t + '-' + a;
- }
- } // Add sugar for fill and stroke
- ;
- ['fill', 'stroke'].forEach(function (m) {
- const extension = {};
- let i;
- extension[m] = function (o) {
- if (typeof o === 'undefined') {
- return this.attr(m);
- }
- if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) {
- this.attr(m, o);
- } else {
- // set all attributes from sugar.fill and sugar.stroke list
- for (i = sugar[m].length - 1; i >= 0; i--) {
- if (o[sugar[m][i]] != null) {
- this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]);
- }
- }
- }
- return this;
- };
- registerMethods(['Element', 'Runner'], extension);
- });
- registerMethods(['Element', 'Runner'], {
- // Let the user set the matrix directly
- matrix: function (mat, b, c, d, e, f) {
- // Act as a getter
- if (mat == null) {
- return new Matrix(this);
- } // Act as a setter, the user can pass a matrix or a set of numbers
- return this.attr('transform', new Matrix(mat, b, c, d, e, f));
- },
- // Map rotation to transform
- rotate: function (angle, cx, cy) {
- return this.transform({
- rotate: angle,
- ox: cx,
- oy: cy
- }, true);
- },
- // Map skew to transform
- skew: function (x, y, cx, cy) {
- return arguments.length === 1 || arguments.length === 3 ? this.transform({
- skew: x,
- ox: y,
- oy: cx
- }, true) : this.transform({
- skew: [x, y],
- ox: cx,
- oy: cy
- }, true);
- },
- shear: function (lam, cx, cy) {
- return this.transform({
- shear: lam,
- ox: cx,
- oy: cy
- }, true);
- },
- // Map scale to transform
- scale: function (x, y, cx, cy) {
- return arguments.length === 1 || arguments.length === 3 ? this.transform({
- scale: x,
- ox: y,
- oy: cx
- }, true) : this.transform({
- scale: [x, y],
- ox: cx,
- oy: cy
- }, true);
- },
- // Map translate to transform
- translate: function (x, y) {
- return this.transform({
- translate: [x, y]
- }, true);
- },
- // Map relative translations to transform
- relative: function (x, y) {
- return this.transform({
- relative: [x, y]
- }, true);
- },
- // Map flip to transform
- flip: function (direction = 'both', origin = 'center') {
- if ('xybothtrue'.indexOf(direction) === -1) {
- origin = direction;
- direction = 'both';
- }
- return this.transform({
- flip: direction,
- origin: origin
- }, true);
- },
- // Opacity
- opacity: function (value) {
- return this.attr('opacity', value);
- }
- });
- registerMethods('radius', {
- // Add x and y radius
- radius: function (x, y = x) {
- const type = (this._element || this).type;
- return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y);
- }
- });
- registerMethods('Path', {
- // Get path length
- length: function () {
- return this.node.getTotalLength();
- },
- // Get point at length
- pointAt: function (length) {
- return new Point(this.node.getPointAtLength(length));
- }
- });
- registerMethods(['Element', 'Runner'], {
- // Set font
- font: function (a, v) {
- if (typeof a === 'object') {
- for (v in a) this.font(v, a[v]);
- return this;
- }
- return a === 'leading' ? this.leading(v) : a === 'anchor' ? this.attr('text-anchor', v) : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' ? this.attr('font-' + a, v) : this.attr(a, v);
- }
- }); // Add events to elements
- const methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
- // add event to Element
- const fn = function (f) {
- if (f === null) {
- this.off(event);
- } else {
- this.on(event, f);
- }
- return this;
- };
- last[event] = fn;
- return last;
- }, {});
- registerMethods('Element', methods);
- function untransform() {
- return this.attr('transform', null);
- } // merge the whole transformation chain into one matrix and returns it
- function matrixify() {
- const matrix = (this.attr('transform') || '' // split transformations
- ).split(transforms).slice(0, -1).map(function (str) {
- // generate key => value pairs
- const kv = str.trim().split('(');
- return [kv[0], kv[1].split(delimiter).map(function (str) {
- return parseFloat(str);
- })];
- }).reverse() // merge every transformation into one matrix
- .reduce(function (matrix, transform) {
- if (transform[0] === 'matrix') {
- return matrix.lmultiply(Matrix.fromArray(transform[1]));
- }
- return matrix[transform[0]].apply(matrix, transform[1]);
- }, new Matrix());
- return matrix;
- } // add an element to another parent without changing the visual representation on the screen
- function toParent(parent, i) {
- if (this === parent) return this;
- const ctm = this.screenCTM();
- const pCtm = parent.screenCTM().inverse();
- this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm));
- return this;
- } // same as above with parent equals root-svg
- function toRoot(i) {
- return this.toParent(this.root(), i);
- } // Add transformations
- function transform(o, relative) {
- // Act as a getter if no object was passed
- if (o == null || typeof o === 'string') {
- const decomposed = new Matrix(this).decompose();
- return o == null ? decomposed : decomposed[o];
- }
- if (!Matrix.isMatrixLike(o)) {
- // Set the origin according to the defined transform
- o = { ...o,
- origin: getOrigin(o, this)
- };
- } // The user can pass a boolean, an Element or an Matrix or nothing
- const cleanRelative = relative === true ? this : relative || false;
- const result = new Matrix(cleanRelative).transform(o);
- return this.attr('transform', result);
- }
- registerMethods('Element', {
- untransform,
- matrixify,
- toParent,
- toRoot,
- transform
- });
- class Container extends Element {
- flatten(parent = this, index) {
- this.each(function () {
- if (this instanceof Container) {
- return this.flatten().ungroup();
- }
- });
- return this;
- }
- ungroup(parent = this.parent(), index = parent.index(this)) {
- // when parent != this, we want append all elements to the end
- index = index === -1 ? parent.children().length : index;
- this.each(function (i, children) {
- // reverse each
- return children[children.length - i - 1].toParent(parent, index);
- });
- return this.remove();
- }
- }
- register(Container, 'Container');
- class Defs extends Container {
- constructor(node, attrs = node) {
- super(nodeOrNew('defs', node), attrs);
- }
- flatten() {
- return this;
- }
- ungroup() {
- return this;
- }
- }
- register(Defs, 'Defs');
- class Shape extends Element {}
- register(Shape, 'Shape');
- function rx(rx) {
- return this.attr('rx', rx);
- } // Radius y value
- function ry(ry) {
- return this.attr('ry', ry);
- } // Move over x-axis
- function x$3(x) {
- return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());
- } // Move over y-axis
- function y$3(y) {
- return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());
- } // Move by center over x-axis
- function cx$1(x) {
- return this.attr('cx', x);
- } // Move by center over y-axis
- function cy$1(y) {
- return this.attr('cy', y);
- } // Set width of element
- function width$2(width) {
- return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));
- } // Set height of element
- function height$2(height) {
- return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
- }
- var circled = {
- __proto__: null,
- rx: rx,
- ry: ry,
- x: x$3,
- y: y$3,
- cx: cx$1,
- cy: cy$1,
- width: width$2,
- height: height$2
- };
- class Ellipse extends Shape {
- constructor(node, attrs = node) {
- super(nodeOrNew('ellipse', node), attrs);
- }
- size(width, height) {
- const p = proportionalSize(this, width, height);
- return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
- }
- }
- extend(Ellipse, circled);
- registerMethods('Container', {
- // Create an ellipse
- ellipse: wrapWithAttrCheck(function (width = 0, height = width) {
- return this.put(new Ellipse()).size(width, height).move(0, 0);
- })
- });
- register(Ellipse, 'Ellipse');
- class Fragment extends Dom {
- constructor(node = globals.document.createDocumentFragment()) {
- super(node);
- } // Import / Export raw xml
- xml(xmlOrFn, outerXML, ns) {
- if (typeof xmlOrFn === 'boolean') {
- ns = outerXML;
- outerXML = xmlOrFn;
- xmlOrFn = null;
- } // because this is a fragment we have to put all elements into a wrapper first
- // before we can get the innerXML from it
- if (xmlOrFn == null || typeof xmlOrFn === 'function') {
- const wrapper = new Dom(create('wrapper', ns));
- wrapper.add(this.node.cloneNode(true));
- return wrapper.xml(false, ns);
- } // Act as setter if we got a string
- return super.xml(xmlOrFn, false, ns);
- }
- }
- register(Fragment, 'Fragment');
- function from(x, y) {
- return (this._element || this).type === 'radialGradient' ? this.attr({
- fx: new SVGNumber(x),
- fy: new SVGNumber(y)
- }) : this.attr({
- x1: new SVGNumber(x),
- y1: new SVGNumber(y)
- });
- }
- function to(x, y) {
- return (this._element || this).type === 'radialGradient' ? this.attr({
- cx: new SVGNumber(x),
- cy: new SVGNumber(y)
- }) : this.attr({
- x2: new SVGNumber(x),
- y2: new SVGNumber(y)
- });
- }
- var gradiented = {
- __proto__: null,
- from: from,
- to: to
- };
- class Gradient extends Container {
- constructor(type, attrs) {
- super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs);
- } // custom attr to handle transform
- attr(a, b, c) {
- if (a === 'transform') a = 'gradientTransform';
- return super.attr(a, b, c);
- }
- bbox() {
- return new Box();
- }
- targets() {
- return baseFind('svg [fill*=' + this.id() + ']');
- } // Alias string conversion to fill
- toString() {
- return this.url();
- } // Update gradient
- update(block) {
- // remove all stops
- this.clear(); // invoke passed block
- if (typeof block === 'function') {
- block.call(this, this);
- }
- return this;
- } // Return the fill id
- url() {
- return 'url(#' + this.id() + ')';
- }
- }
- extend(Gradient, gradiented);
- registerMethods({
- Container: {
- // Create gradient element in defs
- gradient(...args) {
- return this.defs().gradient(...args);
- }
- },
- // define gradient
- Defs: {
- gradient: wrapWithAttrCheck(function (type, block) {
- return this.put(new Gradient(type)).update(block);
- })
- }
- });
- register(Gradient, 'Gradient');
- class Pattern extends Container {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('pattern', node), attrs);
- } // custom attr to handle transform
- attr(a, b, c) {
- if (a === 'transform') a = 'patternTransform';
- return super.attr(a, b, c);
- }
- bbox() {
- return new Box();
- }
- targets() {
- return baseFind('svg [fill*=' + this.id() + ']');
- } // Alias string conversion to fill
- toString() {
- return this.url();
- } // Update pattern by rebuilding
- update(block) {
- // remove content
- this.clear(); // invoke passed block
- if (typeof block === 'function') {
- block.call(this, this);
- }
- return this;
- } // Return the fill id
- url() {
- return 'url(#' + this.id() + ')';
- }
- }
- registerMethods({
- Container: {
- // Create pattern element in defs
- pattern(...args) {
- return this.defs().pattern(...args);
- }
- },
- Defs: {
- pattern: wrapWithAttrCheck(function (width, height, block) {
- return this.put(new Pattern()).update(block).attr({
- x: 0,
- y: 0,
- width: width,
- height: height,
- patternUnits: 'userSpaceOnUse'
- });
- })
- }
- });
- register(Pattern, 'Pattern');
- class Image extends Shape {
- constructor(node, attrs = node) {
- super(nodeOrNew('image', node), attrs);
- } // (re)load image
- load(url, callback) {
- if (!url) return this;
- const img = new globals.window.Image();
- on(img, 'load', function (e) {
- const p = this.parent(Pattern); // ensure image size
- if (this.width() === 0 && this.height() === 0) {
- this.size(img.width, img.height);
- }
- if (p instanceof Pattern) {
- // ensure pattern size if not set
- if (p.width() === 0 && p.height() === 0) {
- p.size(this.width(), this.height());
- }
- }
- if (typeof callback === 'function') {
- callback.call(this, e);
- }
- }, this);
- on(img, 'load error', function () {
- // dont forget to unbind memory leaking events
- off(img);
- });
- return this.attr('href', img.src = url, xlink);
- }
- }
- registerAttrHook(function (attr, val, _this) {
- // convert image fill and stroke to patterns
- if (attr === 'fill' || attr === 'stroke') {
- if (isImage.test(val)) {
- val = _this.root().defs().image(val);
- }
- }
- if (val instanceof Image) {
- val = _this.root().defs().pattern(0, 0, pattern => {
- pattern.add(val);
- });
- }
- return val;
- });
- registerMethods({
- Container: {
- // create image element, load image and set its size
- image: wrapWithAttrCheck(function (source, callback) {
- return this.put(new Image()).size(0, 0).load(source, callback);
- })
- }
- });
- register(Image, 'Image');
- class PointArray extends SVGArray {
- // Get bounding box of points
- bbox() {
- let maxX = -Infinity;
- let maxY = -Infinity;
- let minX = Infinity;
- let minY = Infinity;
- this.forEach(function (el) {
- maxX = Math.max(el[0], maxX);
- maxY = Math.max(el[1], maxY);
- minX = Math.min(el[0], minX);
- minY = Math.min(el[1], minY);
- });
- return new Box(minX, minY, maxX - minX, maxY - minY);
- } // Move point string
- move(x, y) {
- const box = this.bbox(); // get relative offset
- x -= box.x;
- y -= box.y; // move every point
- if (!isNaN(x) && !isNaN(y)) {
- for (let i = this.length - 1; i >= 0; i--) {
- this[i] = [this[i][0] + x, this[i][1] + y];
- }
- }
- return this;
- } // Parse point string and flat array
- parse(array = [0, 0]) {
- const points = []; // if it is an array, we flatten it and therefore clone it to 1 depths
- if (array instanceof Array) {
- array = Array.prototype.concat.apply([], array);
- } else {
- // Else, it is considered as a string
- // parse points
- array = array.trim().split(delimiter).map(parseFloat);
- } // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
- // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
- if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples
- for (let i = 0, len = array.length; i < len; i = i + 2) {
- points.push([array[i], array[i + 1]]);
- }
- return points;
- } // Resize poly string
- size(width, height) {
- let i;
- const box = this.bbox(); // recalculate position of all points according to new size
- for (i = this.length - 1; i >= 0; i--) {
- if (box.width) this[i][0] = (this[i][0] - box.x) * width / box.width + box.x;
- if (box.height) this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
- }
- return this;
- } // Convert array to line object
- toLine() {
- return {
- x1: this[0][0],
- y1: this[0][1],
- x2: this[1][0],
- y2: this[1][1]
- };
- } // Convert array to string
- toString() {
- const array = []; // convert to a poly point string
- for (let i = 0, il = this.length; i < il; i++) {
- array.push(this[i].join(','));
- }
- return array.join(' ');
- }
- transform(m) {
- return this.clone().transformO(m);
- } // transform points with matrix (similar to Point.transform)
- transformO(m) {
- if (!Matrix.isMatrixLike(m)) {
- m = new Matrix(m);
- }
- for (let i = this.length; i--;) {
- // Perform the matrix multiplication
- const [x, y] = this[i];
- this[i][0] = m.a * x + m.c * y + m.e;
- this[i][1] = m.b * x + m.d * y + m.f;
- }
- return this;
- }
- }
- const MorphArray = PointArray; // Move by left top corner over x-axis
- function x$2(x) {
- return x == null ? this.bbox().x : this.move(x, this.bbox().y);
- } // Move by left top corner over y-axis
- function y$2(y) {
- return y == null ? this.bbox().y : this.move(this.bbox().x, y);
- } // Set width of element
- function width$1(width) {
- const b = this.bbox();
- return width == null ? b.width : this.size(width, b.height);
- } // Set height of element
- function height$1(height) {
- const b = this.bbox();
- return height == null ? b.height : this.size(b.width, height);
- }
- var pointed = {
- __proto__: null,
- MorphArray: MorphArray,
- x: x$2,
- y: y$2,
- width: width$1,
- height: height$1
- };
- class Line extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('line', node), attrs);
- } // Get array
- array() {
- return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);
- } // Move by left top corner
- move(x, y) {
- return this.attr(this.array().move(x, y).toLine());
- } // Overwrite native plot() method
- plot(x1, y1, x2, y2) {
- if (x1 == null) {
- return this.array();
- } else if (typeof y1 !== 'undefined') {
- x1 = {
- x1,
- y1,
- x2,
- y2
- };
- } else {
- x1 = new PointArray(x1).toLine();
- }
- return this.attr(x1);
- } // Set element size to given width and height
- size(width, height) {
- const p = proportionalSize(this, width, height);
- return this.attr(this.array().size(p.width, p.height).toLine());
- }
- }
- extend(Line, pointed);
- registerMethods({
- Container: {
- // Create a line element
- line: wrapWithAttrCheck(function (...args) {
- // make sure plot is called as a setter
- // x1 is not necessarily a number, it can also be an array, a string and a PointArray
- return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]);
- })
- }
- });
- register(Line, 'Line');
- class Marker extends Container {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('marker', node), attrs);
- } // Set height of element
- height(height) {
- return this.attr('markerHeight', height);
- }
- orient(orient) {
- return this.attr('orient', orient);
- } // Set marker refX and refY
- ref(x, y) {
- return this.attr('refX', x).attr('refY', y);
- } // Return the fill id
- toString() {
- return 'url(#' + this.id() + ')';
- } // Update marker
- update(block) {
- // remove all content
- this.clear(); // invoke passed block
- if (typeof block === 'function') {
- block.call(this, this);
- }
- return this;
- } // Set width of element
- width(width) {
- return this.attr('markerWidth', width);
- }
- }
- registerMethods({
- Container: {
- marker(...args) {
- // Create marker element in defs
- return this.defs().marker(...args);
- }
- },
- Defs: {
- // Create marker
- marker: wrapWithAttrCheck(function (width, height, block) {
- // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
- return this.put(new Marker()).size(width, height).ref(width / 2, height / 2).viewbox(0, 0, width, height).attr('orient', 'auto').update(block);
- })
- },
- marker: {
- // Create and attach markers
- marker(marker, width, height, block) {
- let attr = ['marker']; // Build attribute name
- if (marker !== 'all') attr.push(marker);
- attr = attr.join('-'); // Set marker attribute
- marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);
- return this.attr(attr, marker);
- }
- }
- });
- register(Marker, 'Marker');
- /***
- Base Class
- ==========
- The base stepper class that will be
- ***/
- function makeSetterGetter(k, f) {
- return function (v) {
- if (v == null) return this[k];
- this[k] = v;
- if (f) f.call(this);
- return this;
- };
- }
- const easing = {
- '-': function (pos) {
- return pos;
- },
- '<>': function (pos) {
- return -Math.cos(pos * Math.PI) / 2 + 0.5;
- },
- '>': function (pos) {
- return Math.sin(pos * Math.PI / 2);
- },
- '<': function (pos) {
- return -Math.cos(pos * Math.PI / 2) + 1;
- },
- bezier: function (x1, y1, x2, y2) {
- // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
- return function (t) {
- if (t < 0) {
- if (x1 > 0) {
- return y1 / x1 * t;
- } else if (x2 > 0) {
- return y2 / x2 * t;
- } else {
- return 0;
- }
- } else if (t > 1) {
- if (x2 < 1) {
- return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2);
- } else if (x1 < 1) {
- return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1);
- } else {
- return 1;
- }
- } else {
- return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3;
- }
- };
- },
- // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
- steps: function (steps, stepPosition = 'end') {
- // deal with "jump-" prefix
- stepPosition = stepPosition.split('-').reverse()[0];
- let jumps = steps;
- if (stepPosition === 'none') {
- --jumps;
- } else if (stepPosition === 'both') {
- ++jumps;
- } // The beforeFlag is essentially useless
- return (t, beforeFlag = false) => {
- // Step is called currentStep in referenced url
- let step = Math.floor(t * steps);
- const jumping = t * step % 1 === 0;
- if (stepPosition === 'start' || stepPosition === 'both') {
- ++step;
- }
- if (beforeFlag && jumping) {
- --step;
- }
- if (t >= 0 && step < 0) {
- step = 0;
- }
- if (t <= 1 && step > jumps) {
- step = jumps;
- }
- return step / jumps;
- };
- }
- };
- class Stepper {
- done() {
- return false;
- }
- }
- /***
- Easing Functions
- ================
- ***/
- class Ease extends Stepper {
- constructor(fn = timeline.ease) {
- super();
- this.ease = easing[fn] || fn;
- }
- step(from, to, pos) {
- if (typeof from !== 'number') {
- return pos < 1 ? from : to;
- }
- return from + (to - from) * this.ease(pos);
- }
- }
- /***
- Controller Types
- ================
- ***/
- class Controller extends Stepper {
- constructor(fn) {
- super();
- this.stepper = fn;
- }
- done(c) {
- return c.done;
- }
- step(current, target, dt, c) {
- return this.stepper(current, target, dt, c);
- }
- }
- function recalculate() {
- // Apply the default parameters
- const duration = (this._duration || 500) / 1000;
- const overshoot = this._overshoot || 0; // Calculate the PID natural response
- const eps = 1e-10;
- const pi = Math.PI;
- const os = Math.log(overshoot / 100 + eps);
- const zeta = -os / Math.sqrt(pi * pi + os * os);
- const wn = 3.9 / (zeta * duration); // Calculate the Spring values
- this.d = 2 * zeta * wn;
- this.k = wn * wn;
- }
- class Spring extends Controller {
- constructor(duration = 500, overshoot = 0) {
- super();
- this.duration(duration).overshoot(overshoot);
- }
- step(current, target, dt, c) {
- if (typeof current === 'string') return current;
- c.done = dt === Infinity;
- if (dt === Infinity) return target;
- if (dt === 0) return current;
- if (dt > 100) dt = 16;
- dt /= 1000; // Get the previous velocity
- const velocity = c.velocity || 0; // Apply the control to get the new position and store it
- const acceleration = -this.d * velocity - this.k * (current - target);
- const newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity
- c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value
- c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
- return c.done ? target : newPosition;
- }
- }
- extend(Spring, {
- duration: makeSetterGetter('_duration', recalculate),
- overshoot: makeSetterGetter('_overshoot', recalculate)
- });
- class PID extends Controller {
- constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) {
- super();
- this.p(p).i(i).d(d).windup(windup);
- }
- step(current, target, dt, c) {
- if (typeof current === 'string') return current;
- c.done = dt === Infinity;
- if (dt === Infinity) return target;
- if (dt === 0) return current;
- const p = target - current;
- let i = (c.integral || 0) + p * dt;
- const d = (p - (c.error || 0)) / dt;
- const windup = this._windup; // antiwindup
- if (windup !== false) {
- i = Math.max(-windup, Math.min(i, windup));
- }
- c.error = p;
- c.integral = i;
- c.done = Math.abs(p) < 0.001;
- return c.done ? target : current + (this.P * p + this.I * i + this.D * d);
- }
- }
- extend(PID, {
- windup: makeSetterGetter('_windup'),
- p: makeSetterGetter('P'),
- i: makeSetterGetter('I'),
- d: makeSetterGetter('D')
- });
- const segmentParameters = {
- M: 2,
- L: 2,
- H: 1,
- V: 1,
- C: 6,
- S: 4,
- Q: 4,
- T: 2,
- A: 7,
- Z: 0
- };
- const pathHandlers = {
- M: function (c, p, p0) {
- p.x = p0.x = c[0];
- p.y = p0.y = c[1];
- return ['M', p.x, p.y];
- },
- L: function (c, p) {
- p.x = c[0];
- p.y = c[1];
- return ['L', c[0], c[1]];
- },
- H: function (c, p) {
- p.x = c[0];
- return ['H', c[0]];
- },
- V: function (c, p) {
- p.y = c[0];
- return ['V', c[0]];
- },
- C: function (c, p) {
- p.x = c[4];
- p.y = c[5];
- return ['C', c[0], c[1], c[2], c[3], c[4], c[5]];
- },
- S: function (c, p) {
- p.x = c[2];
- p.y = c[3];
- return ['S', c[0], c[1], c[2], c[3]];
- },
- Q: function (c, p) {
- p.x = c[2];
- p.y = c[3];
- return ['Q', c[0], c[1], c[2], c[3]];
- },
- T: function (c, p) {
- p.x = c[0];
- p.y = c[1];
- return ['T', c[0], c[1]];
- },
- Z: function (c, p, p0) {
- p.x = p0.x;
- p.y = p0.y;
- return ['Z'];
- },
- A: function (c, p) {
- p.x = c[5];
- p.y = c[6];
- return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
- }
- };
- const mlhvqtcsaz = 'mlhvqtcsaz'.split('');
- for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
- pathHandlers[mlhvqtcsaz[i]] = function (i) {
- return function (c, p, p0) {
- if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
- c[5] = c[5] + p.x;
- c[6] = c[6] + p.y;
- } else {
- for (let j = 0, jl = c.length; j < jl; ++j) {
- c[j] = c[j] + (j % 2 ? p.y : p.x);
- }
- }
- return pathHandlers[i](c, p, p0);
- };
- }(mlhvqtcsaz[i].toUpperCase());
- }
- function makeAbsolut(parser) {
- const command = parser.segment[0];
- return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0);
- }
- function segmentComplete(parser) {
- return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()];
- }
- function startNewSegment(parser, token) {
- parser.inNumber && finalizeNumber(parser, false);
- const pathLetter = isPathLetter.test(token);
- if (pathLetter) {
- parser.segment = [token];
- } else {
- const lastCommand = parser.lastCommand;
- const small = lastCommand.toLowerCase();
- const isSmall = lastCommand === small;
- parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand];
- }
- parser.inSegment = true;
- parser.lastCommand = parser.segment[0];
- return pathLetter;
- }
- function finalizeNumber(parser, inNumber) {
- if (!parser.inNumber) throw new Error('Parser Error');
- parser.number && parser.segment.push(parseFloat(parser.number));
- parser.inNumber = inNumber;
- parser.number = '';
- parser.pointSeen = false;
- parser.hasExponent = false;
- if (segmentComplete(parser)) {
- finalizeSegment(parser);
- }
- }
- function finalizeSegment(parser) {
- parser.inSegment = false;
- if (parser.absolute) {
- parser.segment = makeAbsolut(parser);
- }
- parser.segments.push(parser.segment);
- }
- function isArcFlag(parser) {
- if (!parser.segment.length) return false;
- const isArc = parser.segment[0].toUpperCase() === 'A';
- const length = parser.segment.length;
- return isArc && (length === 4 || length === 5);
- }
- function isExponential(parser) {
- return parser.lastToken.toUpperCase() === 'E';
- }
- function pathParser(d, toAbsolute = true) {
- let index = 0;
- let token = '';
- const parser = {
- segment: [],
- inNumber: false,
- number: '',
- lastToken: '',
- inSegment: false,
- segments: [],
- pointSeen: false,
- hasExponent: false,
- absolute: toAbsolute,
- p0: new Point(),
- p: new Point()
- };
- while (parser.lastToken = token, token = d.charAt(index++)) {
- if (!parser.inSegment) {
- if (startNewSegment(parser, token)) {
- continue;
- }
- }
- if (token === '.') {
- if (parser.pointSeen || parser.hasExponent) {
- finalizeNumber(parser, false);
- --index;
- continue;
- }
- parser.inNumber = true;
- parser.pointSeen = true;
- parser.number += token;
- continue;
- }
- if (!isNaN(parseInt(token))) {
- if (parser.number === '0' || isArcFlag(parser)) {
- parser.inNumber = true;
- parser.number = token;
- finalizeNumber(parser, true);
- continue;
- }
- parser.inNumber = true;
- parser.number += token;
- continue;
- }
- if (token === ' ' || token === ',') {
- if (parser.inNumber) {
- finalizeNumber(parser, false);
- }
- continue;
- }
- if (token === '-') {
- if (parser.inNumber && !isExponential(parser)) {
- finalizeNumber(parser, false);
- --index;
- continue;
- }
- parser.number += token;
- parser.inNumber = true;
- continue;
- }
- if (token.toUpperCase() === 'E') {
- parser.number += token;
- parser.hasExponent = true;
- continue;
- }
- if (isPathLetter.test(token)) {
- if (parser.inNumber) {
- finalizeNumber(parser, false);
- } else if (!segmentComplete(parser)) {
- throw new Error('parser Error');
- } else {
- finalizeSegment(parser);
- }
- --index;
- }
- }
- if (parser.inNumber) {
- finalizeNumber(parser, false);
- }
- if (parser.inSegment && segmentComplete(parser)) {
- finalizeSegment(parser);
- }
- return parser.segments;
- }
- function arrayToString(a) {
- let s = '';
- for (let i = 0, il = a.length; i < il; i++) {
- s += a[i][0];
- if (a[i][1] != null) {
- s += a[i][1];
- if (a[i][2] != null) {
- s += ' ';
- s += a[i][2];
- if (a[i][3] != null) {
- s += ' ';
- s += a[i][3];
- s += ' ';
- s += a[i][4];
- if (a[i][5] != null) {
- s += ' ';
- s += a[i][5];
- s += ' ';
- s += a[i][6];
- if (a[i][7] != null) {
- s += ' ';
- s += a[i][7];
- }
- }
- }
- }
- }
- }
- return s + ' ';
- }
- class PathArray extends SVGArray {
- // Get bounding box of path
- bbox() {
- parser().path.setAttribute('d', this.toString());
- return new Box(parser.nodes.path.getBBox());
- } // Move path string
- move(x, y) {
- // get bounding box of current situation
- const box = this.bbox(); // get relative offset
- x -= box.x;
- y -= box.y;
- if (!isNaN(x) && !isNaN(y)) {
- // move every point
- for (let l, i = this.length - 1; i >= 0; i--) {
- l = this[i][0];
- if (l === 'M' || l === 'L' || l === 'T') {
- this[i][1] += x;
- this[i][2] += y;
- } else if (l === 'H') {
- this[i][1] += x;
- } else if (l === 'V') {
- this[i][1] += y;
- } else if (l === 'C' || l === 'S' || l === 'Q') {
- this[i][1] += x;
- this[i][2] += y;
- this[i][3] += x;
- this[i][4] += y;
- if (l === 'C') {
- this[i][5] += x;
- this[i][6] += y;
- }
- } else if (l === 'A') {
- this[i][6] += x;
- this[i][7] += y;
- }
- }
- }
- return this;
- } // Absolutize and parse path to array
- parse(d = 'M0 0') {
- if (Array.isArray(d)) {
- d = Array.prototype.concat.apply([], d).toString();
- }
- return pathParser(d);
- } // Resize path string
- size(width, height) {
- // get bounding box of current situation
- const box = this.bbox();
- let i, l; // If the box width or height is 0 then we ignore
- // transformations on the respective axis
- box.width = box.width === 0 ? 1 : box.width;
- box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size
- for (i = this.length - 1; i >= 0; i--) {
- l = this[i][0];
- if (l === 'M' || l === 'L' || l === 'T') {
- this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
- this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
- } else if (l === 'H') {
- this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
- } else if (l === 'V') {
- this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
- } else if (l === 'C' || l === 'S' || l === 'Q') {
- this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
- this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
- this[i][3] = (this[i][3] - box.x) * width / box.width + box.x;
- this[i][4] = (this[i][4] - box.y) * height / box.height + box.y;
- if (l === 'C') {
- this[i][5] = (this[i][5] - box.x) * width / box.width + box.x;
- this[i][6] = (this[i][6] - box.y) * height / box.height + box.y;
- }
- } else if (l === 'A') {
- // resize radii
- this[i][1] = this[i][1] * width / box.width;
- this[i][2] = this[i][2] * height / box.height; // move position values
- this[i][6] = (this[i][6] - box.x) * width / box.width + box.x;
- this[i][7] = (this[i][7] - box.y) * height / box.height + box.y;
- }
- }
- return this;
- } // Convert array to string
- toString() {
- return arrayToString(this);
- }
- }
- const getClassForType = value => {
- const type = typeof value;
- if (type === 'number') {
- return SVGNumber;
- } else if (type === 'string') {
- if (Color.isColor(value)) {
- return Color;
- } else if (delimiter.test(value)) {
- return isPathLetter.test(value) ? PathArray : SVGArray;
- } else if (numberAndUnit.test(value)) {
- return SVGNumber;
- } else {
- return NonMorphable;
- }
- } else if (morphableTypes.indexOf(value.constructor) > -1) {
- return value.constructor;
- } else if (Array.isArray(value)) {
- return SVGArray;
- } else if (type === 'object') {
- return ObjectBag;
- } else {
- return NonMorphable;
- }
- };
- class Morphable {
- constructor(stepper) {
- this._stepper = stepper || new Ease('-');
- this._from = null;
- this._to = null;
- this._type = null;
- this._context = null;
- this._morphObj = null;
- }
- at(pos) {
- return this._morphObj.morph(this._from, this._to, pos, this._stepper, this._context);
- }
- done() {
- const complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
- return last && curr;
- }, true);
- return complete;
- }
- from(val) {
- if (val == null) {
- return this._from;
- }
- this._from = this._set(val);
- return this;
- }
- stepper(stepper) {
- if (stepper == null) return this._stepper;
- this._stepper = stepper;
- return this;
- }
- to(val) {
- if (val == null) {
- return this._to;
- }
- this._to = this._set(val);
- return this;
- }
- type(type) {
- // getter
- if (type == null) {
- return this._type;
- } // setter
- this._type = type;
- return this;
- }
- _set(value) {
- if (!this._type) {
- this.type(getClassForType(value));
- }
- let result = new this._type(value);
- if (this._type === Color) {
- result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result;
- }
- if (this._type === ObjectBag) {
- result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result;
- }
- result = result.toConsumable();
- this._morphObj = this._morphObj || new this._type();
- this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) {
- o.done = true;
- return o;
- });
- return result;
- }
- }
- class NonMorphable {
- constructor(...args) {
- this.init(...args);
- }
- init(val) {
- val = Array.isArray(val) ? val[0] : val;
- this.value = val;
- return this;
- }
- toArray() {
- return [this.value];
- }
- valueOf() {
- return this.value;
- }
- }
- class TransformBag {
- constructor(...args) {
- this.init(...args);
- }
- init(obj) {
- if (Array.isArray(obj)) {
- obj = {
- scaleX: obj[0],
- scaleY: obj[1],
- shear: obj[2],
- rotate: obj[3],
- translateX: obj[4],
- translateY: obj[5],
- originX: obj[6],
- originY: obj[7]
- };
- }
- Object.assign(this, TransformBag.defaults, obj);
- return this;
- }
- toArray() {
- const v = this;
- return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
- }
- }
- TransformBag.defaults = {
- scaleX: 1,
- scaleY: 1,
- shear: 0,
- rotate: 0,
- translateX: 0,
- translateY: 0,
- originX: 0,
- originY: 0
- };
- const sortByKey = (a, b) => {
- return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
- };
- class ObjectBag {
- constructor(...args) {
- this.init(...args);
- }
- align(other) {
- const values = this.values;
- for (let i = 0, il = values.length; i < il; ++i) {
- // If the type is the same we only need to check if the color is in the correct format
- if (values[i + 1] === other[i + 1]) {
- if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) {
- const space = other[i + 7];
- const color = new Color(this.values.splice(i + 3, 5))[space]().toArray();
- this.values.splice(i + 3, 0, ...color);
- }
- i += values[i + 2] + 2;
- continue;
- }
- if (!other[i + 1]) {
- return this;
- } // The types differ, so we overwrite the new type with the old one
- // And initialize it with the types default (e.g. black for color or 0 for number)
- const defaultObject = new other[i + 1]().toArray(); // Than we fix the values array
- const toDelete = values[i + 2] + 3;
- values.splice(i, toDelete, other[i], other[i + 1], other[i + 2], ...defaultObject);
- i += values[i + 2] + 2;
- }
- return this;
- }
- init(objOrArr) {
- this.values = [];
- if (Array.isArray(objOrArr)) {
- this.values = objOrArr.slice();
- return;
- }
- objOrArr = objOrArr || {};
- const entries = [];
- for (const i in objOrArr) {
- const Type = getClassForType(objOrArr[i]);
- const val = new Type(objOrArr[i]).toArray();
- entries.push([i, Type, val.length, ...val]);
- }
- entries.sort(sortByKey);
- this.values = entries.reduce((last, curr) => last.concat(curr), []);
- return this;
- }
- toArray() {
- return this.values;
- }
- valueOf() {
- const obj = {};
- const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) {
- while (arr.length) {
- const key = arr.shift();
- const Type = arr.shift();
- const num = arr.shift();
- const values = arr.splice(0, num);
- obj[key] = new Type(values); // .valueOf()
- }
- return obj;
- }
- }
- const morphableTypes = [NonMorphable, TransformBag, ObjectBag];
- function registerMorphableType(type = []) {
- morphableTypes.push(...[].concat(type));
- }
- function makeMorphable() {
- extend(morphableTypes, {
- to(val) {
- return new Morphable().type(this.constructor).from(this.toArray()) // this.valueOf())
- .to(val);
- },
- fromArray(arr) {
- this.init(arr);
- return this;
- },
- toConsumable() {
- return this.toArray();
- },
- morph(from, to, pos, stepper, context) {
- const mapper = function (i, index) {
- return stepper.step(i, to[index], pos, context[index], context);
- };
- return this.fromArray(from.map(mapper));
- }
- });
- }
- class Path extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('path', node), attrs);
- } // Get array
- array() {
- return this._array || (this._array = new PathArray(this.attr('d')));
- } // Clear array cache
- clear() {
- delete this._array;
- return this;
- } // Set height of element
- height(height) {
- return height == null ? this.bbox().height : this.size(this.bbox().width, height);
- } // Move by left top corner
- move(x, y) {
- return this.attr('d', this.array().move(x, y));
- } // Plot new path
- plot(d) {
- return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
- } // Set element size to given width and height
- size(width, height) {
- const p = proportionalSize(this, width, height);
- return this.attr('d', this.array().size(p.width, p.height));
- } // Set width of element
- width(width) {
- return width == null ? this.bbox().width : this.size(width, this.bbox().height);
- } // Move by left top corner over x-axis
- x(x) {
- return x == null ? this.bbox().x : this.move(x, this.bbox().y);
- } // Move by left top corner over y-axis
- y(y) {
- return y == null ? this.bbox().y : this.move(this.bbox().x, y);
- }
- } // Define morphable array
- Path.prototype.MorphArray = PathArray; // Add parent method
- registerMethods({
- Container: {
- // Create a wrapped path element
- path: wrapWithAttrCheck(function (d) {
- // make sure plot is called as a setter
- return this.put(new Path()).plot(d || new PathArray());
- })
- }
- });
- register(Path, 'Path');
- function array() {
- return this._array || (this._array = new PointArray(this.attr('points')));
- } // Clear array cache
- function clear() {
- delete this._array;
- return this;
- } // Move by left top corner
- function move$2(x, y) {
- return this.attr('points', this.array().move(x, y));
- } // Plot new path
- function plot(p) {
- return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
- } // Set element size to given width and height
- function size$1(width, height) {
- const p = proportionalSize(this, width, height);
- return this.attr('points', this.array().size(p.width, p.height));
- }
- var poly = {
- __proto__: null,
- array: array,
- clear: clear,
- move: move$2,
- plot: plot,
- size: size$1
- };
- class Polygon extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('polygon', node), attrs);
- }
- }
- registerMethods({
- Container: {
- // Create a wrapped polygon element
- polygon: wrapWithAttrCheck(function (p) {
- // make sure plot is called as a setter
- return this.put(new Polygon()).plot(p || new PointArray());
- })
- }
- });
- extend(Polygon, pointed);
- extend(Polygon, poly);
- register(Polygon, 'Polygon');
- class Polyline extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('polyline', node), attrs);
- }
- }
- registerMethods({
- Container: {
- // Create a wrapped polygon element
- polyline: wrapWithAttrCheck(function (p) {
- // make sure plot is called as a setter
- return this.put(new Polyline()).plot(p || new PointArray());
- })
- }
- });
- extend(Polyline, pointed);
- extend(Polyline, poly);
- register(Polyline, 'Polyline');
- class Rect extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('rect', node), attrs);
- }
- }
- extend(Rect, {
- rx,
- ry
- });
- registerMethods({
- Container: {
- // Create a rect element
- rect: wrapWithAttrCheck(function (width, height) {
- return this.put(new Rect()).size(width, height);
- })
- }
- });
- register(Rect, 'Rect');
- class Queue {
- constructor() {
- this._first = null;
- this._last = null;
- } // Shows us the first item in the list
- first() {
- return this._first && this._first.value;
- } // Shows us the last item in the list
- last() {
- return this._last && this._last.value;
- }
- push(value) {
- // An item stores an id and the provided value
- const item = typeof value.next !== 'undefined' ? value : {
- value: value,
- next: null,
- prev: null
- }; // Deal with the queue being empty or populated
- if (this._last) {
- item.prev = this._last;
- this._last.next = item;
- this._last = item;
- } else {
- this._last = item;
- this._first = item;
- } // Return the current item
- return item;
- } // Removes the item that was returned from the push
- remove(item) {
- // Relink the previous item
- if (item.prev) item.prev.next = item.next;
- if (item.next) item.next.prev = item.prev;
- if (item === this._last) this._last = item.prev;
- if (item === this._first) this._first = item.next; // Invalidate item
- item.prev = null;
- item.next = null;
- }
- shift() {
- // Check if we have a value
- const remove = this._first;
- if (!remove) return null; // If we do, remove it and relink things
- this._first = remove.next;
- if (this._first) this._first.prev = null;
- this._last = this._first ? this._last : null;
- return remove.value;
- }
- }
- const Animator = {
- nextDraw: null,
- frames: new Queue(),
- timeouts: new Queue(),
- immediates: new Queue(),
- timer: () => globals.window.performance || globals.window.Date,
- transforms: [],
- frame(fn) {
- // Store the node
- const node = Animator.frames.push({
- run: fn
- }); // Request an animation frame if we don't have one
- if (Animator.nextDraw === null) {
- Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
- } // Return the node so we can remove it easily
- return node;
- },
- timeout(fn, delay) {
- delay = delay || 0; // Work out when the event should fire
- const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue
- const node = Animator.timeouts.push({
- run: fn,
- time: time
- }); // Request another animation frame if we need one
- if (Animator.nextDraw === null) {
- Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
- }
- return node;
- },
- immediate(fn) {
- // Add the immediate fn to the end of the queue
- const node = Animator.immediates.push(fn); // Request another animation frame if we need one
- if (Animator.nextDraw === null) {
- Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
- }
- return node;
- },
- cancelFrame(node) {
- node != null && Animator.frames.remove(node);
- },
- clearTimeout(node) {
- node != null && Animator.timeouts.remove(node);
- },
- cancelImmediate(node) {
- node != null && Animator.immediates.remove(node);
- },
- _draw(now) {
- // Run all the timeouts we can run, if they are not ready yet, add them
- // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
- let nextTimeout = null;
- const lastTimeout = Animator.timeouts.last();
- while (nextTimeout = Animator.timeouts.shift()) {
- // Run the timeout if its time, or push it to the end
- if (now >= nextTimeout.time) {
- nextTimeout.run();
- } else {
- Animator.timeouts.push(nextTimeout);
- } // If we hit the last item, we should stop shifting out more items
- if (nextTimeout === lastTimeout) break;
- } // Run all of the animation frames
- let nextFrame = null;
- const lastFrame = Animator.frames.last();
- while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
- nextFrame.run(now);
- }
- let nextImmediate = null;
- while (nextImmediate = Animator.immediates.shift()) {
- nextImmediate();
- } // If we have remaining timeouts or frames, draw until we don't anymore
- Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;
- }
- };
- const makeSchedule = function (runnerInfo) {
- const start = runnerInfo.start;
- const duration = runnerInfo.runner.duration();
- const end = start + duration;
- return {
- start: start,
- duration: duration,
- end: end,
- runner: runnerInfo.runner
- };
- };
- const defaultSource = function () {
- const w = globals.window;
- return (w.performance || w.Date).now();
- };
- class Timeline extends EventTarget {
- // Construct a new timeline on the given element
- constructor(timeSource = defaultSource) {
- super();
- this._timeSource = timeSource; // Store the timing variables
- this._startTime = 0;
- this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false
- this._persist = 0; // Keep track of the running animations and their starting parameters
- this._nextFrame = null;
- this._paused = true;
- this._runners = [];
- this._runnerIds = [];
- this._lastRunnerId = -1;
- this._time = 0;
- this._lastSourceTime = 0;
- this._lastStepTime = 0; // Make sure that step is always called in class context
- this._step = this._stepFn.bind(this, false);
- this._stepImmediate = this._stepFn.bind(this, true);
- }
- active() {
- return !!this._nextFrame;
- }
- finish() {
- // Go to end and pause
- this.time(this.getEndTimeOfTimeline() + 1);
- return this.pause();
- } // Calculates the end of the timeline
- getEndTime() {
- const lastRunnerInfo = this.getLastRunnerInfo();
- const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0;
- const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
- return lastStartTime + lastDuration;
- }
- getEndTimeOfTimeline() {
- const endTimes = this._runners.map(i => i.start + i.runner.duration());
- return Math.max(0, ...endTimes);
- }
- getLastRunnerInfo() {
- return this.getRunnerInfoById(this._lastRunnerId);
- }
- getRunnerInfoById(id) {
- return this._runners[this._runnerIds.indexOf(id)] || null;
- }
- pause() {
- this._paused = true;
- return this._continue();
- }
- persist(dtOrForever) {
- if (dtOrForever == null) return this._persist;
- this._persist = dtOrForever;
- return this;
- }
- play() {
- // Now make sure we are not paused and continue the animation
- this._paused = false;
- return this.updateTime()._continue();
- }
- reverse(yes) {
- const currentSpeed = this.speed();
- if (yes == null) return this.speed(-currentSpeed);
- const positive = Math.abs(currentSpeed);
- return this.speed(yes ? -positive : positive);
- } // schedules a runner on the timeline
- schedule(runner, delay, when) {
- if (runner == null) {
- return this._runners.map(makeSchedule);
- } // The start time for the next animation can either be given explicitly,
- // derived from the current timeline time or it can be relative to the
- // last start time to chain animations directly
- let absoluteStartTime = 0;
- const endTime = this.getEndTime();
- delay = delay || 0; // Work out when to start the animation
- if (when == null || when === 'last' || when === 'after') {
- // Take the last time and increment
- absoluteStartTime = endTime;
- } else if (when === 'absolute' || when === 'start') {
- absoluteStartTime = delay;
- delay = 0;
- } else if (when === 'now') {
- absoluteStartTime = this._time;
- } else if (when === 'relative') {
- const runnerInfo = this.getRunnerInfoById(runner.id);
- if (runnerInfo) {
- absoluteStartTime = runnerInfo.start + delay;
- delay = 0;
- }
- } else if (when === 'with-last') {
- const lastRunnerInfo = this.getLastRunnerInfo();
- const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
- absoluteStartTime = lastStartTime;
- } else {
- throw new Error('Invalid value for the "when" parameter');
- } // Manage runner
- runner.unschedule();
- runner.timeline(this);
- const persist = runner.persist();
- const runnerInfo = {
- persist: persist === null ? this._persist : persist,
- start: absoluteStartTime + delay,
- runner
- };
- this._lastRunnerId = runner.id;
- this._runners.push(runnerInfo);
- this._runners.sort((a, b) => a.start - b.start);
- this._runnerIds = this._runners.map(info => info.runner.id);
- this.updateTime()._continue();
- return this;
- }
- seek(dt) {
- return this.time(this._time + dt);
- }
- source(fn) {
- if (fn == null) return this._timeSource;
- this._timeSource = fn;
- return this;
- }
- speed(speed) {
- if (speed == null) return this._speed;
- this._speed = speed;
- return this;
- }
- stop() {
- // Go to start and pause
- this.time(0);
- return this.pause();
- }
- time(time) {
- if (time == null) return this._time;
- this._time = time;
- return this._continue(true);
- } // Remove the runner from this timeline
- unschedule(runner) {
- const index = this._runnerIds.indexOf(runner.id);
- if (index < 0) return this;
- this._runners.splice(index, 1);
- this._runnerIds.splice(index, 1);
- runner.timeline(null);
- return this;
- } // Makes sure, that after pausing the time doesn't jump
- updateTime() {
- if (!this.active()) {
- this._lastSourceTime = this._timeSource();
- }
- return this;
- } // Checks if we are running and continues the animation
- _continue(immediateStep = false) {
- Animator.cancelFrame(this._nextFrame);
- this._nextFrame = null;
- if (immediateStep) return this._stepImmediate();
- if (this._paused) return this;
- this._nextFrame = Animator.frame(this._step);
- return this;
- }
- _stepFn(immediateStep = false) {
- // Get the time delta from the last time and update the time
- const time = this._timeSource();
- let dtSource = time - this._lastSourceTime;
- if (immediateStep) dtSource = 0;
- const dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
- this._lastSourceTime = time; // Only update the time if we use the timeSource.
- // Otherwise use the current time
- if (!immediateStep) {
- // Update the time
- this._time += dtTime;
- this._time = this._time < 0 ? 0 : this._time;
- }
- this._lastStepTime = this._time;
- this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time
- // is now before the startTime of the runner. That is why we need to set
- // the runner to position 0
- // FIXME:
- // However, resetting in insertion order leads to bugs. Considering the case,
- // where 2 runners change the same attribute but in different times,
- // resetting both of them will lead to the case where the later defined
- // runner always wins the reset even if the other runner started earlier
- // and therefore should win the attribute battle
- // this can be solved by resetting them backwards
- for (let k = this._runners.length; k--;) {
- // Get and run the current runner and ignore it if its inactive
- const runnerInfo = this._runners[k];
- const runner = runnerInfo.runner; // Make sure that we give the actual difference
- // between runner start time and now
- const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
- // and try to reset it
- if (dtToStart <= 0) {
- runner.reset();
- }
- } // Run all of the runners directly
- let runnersLeft = false;
- for (let i = 0, len = this._runners.length; i < len; i++) {
- // Get and run the current runner and ignore it if its inactive
- const runnerInfo = this._runners[i];
- const runner = runnerInfo.runner;
- let dt = dtTime; // Make sure that we give the actual difference
- // between runner start time and now
- const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
- if (dtToStart <= 0) {
- runnersLeft = true;
- continue;
- } else if (dtToStart < dt) {
- // Adjust dt to make sure that animation is on point
- dt = dtToStart;
- }
- if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
- // frame, otherwise, remove the completed runner
- const finished = runner.step(dt).done;
- if (!finished) {
- runnersLeft = true; // continue
- } else if (runnerInfo.persist !== true) {
- // runner is finished. And runner might get removed
- const endTime = runner.duration() - runner.time() + this._time;
- if (endTime + runnerInfo.persist < this._time) {
- // Delete runner and correct index
- runner.unschedule();
- --i;
- --len;
- }
- }
- } // Basically: we continue when there are runners right from us in time
- // when -->, and when runners are left from us when <--
- if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) {
- this._continue();
- } else {
- this.pause();
- this.fire('finished');
- }
- return this;
- }
- }
- registerMethods({
- Element: {
- timeline: function (timeline) {
- if (timeline == null) {
- this._timeline = this._timeline || new Timeline();
- return this._timeline;
- } else {
- this._timeline = timeline;
- return this;
- }
- }
- }
- });
- class Runner extends EventTarget {
- constructor(options) {
- super(); // Store a unique id on the runner, so that we can identify it later
- this.id = Runner.id++; // Ensure a default value
- options = options == null ? timeline.duration : options; // Ensure that we get a controller
- options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables
- this._element = null;
- this._timeline = null;
- this.done = false;
- this._queue = []; // Work out the stepper and the duration
- this._duration = typeof options === 'number' && options;
- this._isDeclarative = options instanceof Controller;
- this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change
- this._history = {}; // Store the state of the runner
- this.enabled = true;
- this._time = 0;
- this._lastTime = 0; // At creation, the runner is in reset state
- this._reseted = true; // Save transforms applied to this runner
- this.transforms = new Matrix();
- this.transformId = 1; // Looping variables
- this._haveReversed = false;
- this._reverse = false;
- this._loopsDone = 0;
- this._swing = false;
- this._wait = 0;
- this._times = 1;
- this._frameId = null; // Stores how long a runner is stored after being done
- this._persist = this._isDeclarative ? true : null;
- }
- static sanitise(duration, delay, when) {
- // Initialise the default parameters
- let times = 1;
- let swing = false;
- let wait = 0;
- duration = duration || timeline.duration;
- delay = delay || timeline.delay;
- when = when || 'last'; // If we have an object, unpack the values
- if (typeof duration === 'object' && !(duration instanceof Stepper)) {
- delay = duration.delay || delay;
- when = duration.when || when;
- swing = duration.swing || swing;
- times = duration.times || times;
- wait = duration.wait || wait;
- duration = duration.duration || timeline.duration;
- }
- return {
- duration: duration,
- delay: delay,
- swing: swing,
- times: times,
- wait: wait,
- when: when
- };
- }
- active(enabled) {
- if (enabled == null) return this.enabled;
- this.enabled = enabled;
- return this;
- }
- /*
- Private Methods
- ===============
- Methods that shouldn't be used externally
- */
- addTransform(transform, index) {
- this.transforms.lmultiplyO(transform);
- return this;
- }
- after(fn) {
- return this.on('finished', fn);
- }
- animate(duration, delay, when) {
- const o = Runner.sanitise(duration, delay, when);
- const runner = new Runner(o.duration);
- if (this._timeline) runner.timeline(this._timeline);
- if (this._element) runner.element(this._element);
- return runner.loop(o).schedule(o.delay, o.when);
- }
- clearTransform() {
- this.transforms = new Matrix();
- return this;
- } // TODO: Keep track of all transformations so that deletion is faster
- clearTransformsFromQueue() {
- if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
- this._queue = this._queue.filter(item => {
- return !item.isTransform;
- });
- }
- }
- delay(delay) {
- return this.animate(0, delay);
- }
- duration() {
- return this._times * (this._wait + this._duration) - this._wait;
- }
- during(fn) {
- return this.queue(null, fn);
- }
- ease(fn) {
- this._stepper = new Ease(fn);
- return this;
- }
- /*
- Runner Definitions
- ==================
- These methods help us define the runtime behaviour of the Runner or they
- help us make new runners from the current runner
- */
- element(element) {
- if (element == null) return this._element;
- this._element = element;
- element._prepareRunner();
- return this;
- }
- finish() {
- return this.step(Infinity);
- }
- loop(times, swing, wait) {
- // Deal with the user passing in an object
- if (typeof times === 'object') {
- swing = times.swing;
- wait = times.wait;
- times = times.times;
- } // Sanitise the values and store them
- this._times = times || Infinity;
- this._swing = swing || false;
- this._wait = wait || 0; // Allow true to be passed
- if (this._times === true) {
- this._times = Infinity;
- }
- return this;
- }
- loops(p) {
- const loopDuration = this._duration + this._wait;
- if (p == null) {
- const loopsDone = Math.floor(this._time / loopDuration);
- const relativeTime = this._time - loopsDone * loopDuration;
- const position = relativeTime / this._duration;
- return Math.min(loopsDone + position, this._times);
- }
- const whole = Math.floor(p);
- const partial = p % 1;
- const time = loopDuration * whole + this._duration * partial;
- return this.time(time);
- }
- persist(dtOrForever) {
- if (dtOrForever == null) return this._persist;
- this._persist = dtOrForever;
- return this;
- }
- position(p) {
- // Get all of the variables we need
- const x = this._time;
- const d = this._duration;
- const w = this._wait;
- const t = this._times;
- const s = this._swing;
- const r = this._reverse;
- let position;
- if (p == null) {
- /*
- This function converts a time to a position in the range [0, 1]
- The full explanation can be found in this desmos demonstration
- https://www.desmos.com/calculator/u4fbavgche
- The logic is slightly simplified here because we can use booleans
- */
- // Figure out the value without thinking about the start or end time
- const f = function (x) {
- const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d));
- const backwards = swinging && !r || !swinging && r;
- const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards;
- const clipped = Math.max(Math.min(uncliped, 1), 0);
- return clipped;
- }; // Figure out the value by incorporating the start time
- const endTime = t * (w + d) - w;
- position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5));
- return position;
- } // Work out the loops done and add the position to the loops done
- const loopsDone = Math.floor(this.loops());
- const swingForward = s && loopsDone % 2 === 0;
- const forwards = swingForward && !r || r && swingForward;
- position = loopsDone + (forwards ? p : 1 - p);
- return this.loops(position);
- }
- progress(p) {
- if (p == null) {
- return Math.min(1, this._time / this.duration());
- }
- return this.time(p * this.duration());
- }
- /*
- Basic Functionality
- ===================
- These methods allow us to attach basic functions to the runner directly
- */
- queue(initFn, runFn, retargetFn, isTransform) {
- this._queue.push({
- initialiser: initFn || noop,
- runner: runFn || noop,
- retarget: retargetFn,
- isTransform: isTransform,
- initialised: false,
- finished: false
- });
- const timeline = this.timeline();
- timeline && this.timeline()._continue();
- return this;
- }
- reset() {
- if (this._reseted) return this;
- this.time(0);
- this._reseted = true;
- return this;
- }
- reverse(reverse) {
- this._reverse = reverse == null ? !this._reverse : reverse;
- return this;
- }
- schedule(timeline, delay, when) {
- // The user doesn't need to pass a timeline if we already have one
- if (!(timeline instanceof Timeline)) {
- when = delay;
- delay = timeline;
- timeline = this.timeline();
- } // If there is no timeline, yell at the user...
- if (!timeline) {
- throw Error('Runner cannot be scheduled without timeline');
- } // Schedule the runner on the timeline provided
- timeline.schedule(this, delay, when);
- return this;
- }
- step(dt) {
- // If we are inactive, this stepper just gets skipped
- if (!this.enabled) return this; // Update the time and get the new position
- dt = dt == null ? 16 : dt;
- this._time += dt;
- const position = this.position(); // Figure out if we need to run the stepper in this frame
- const running = this._lastPosition !== position && this._time >= 0;
- this._lastPosition = position; // Figure out if we just started
- const duration = this.duration();
- const justStarted = this._lastTime <= 0 && this._time > 0;
- const justFinished = this._lastTime < duration && this._time >= duration;
- this._lastTime = this._time;
- if (justStarted) {
- this.fire('start', this);
- } // Work out if the runner is finished set the done flag here so animations
- // know, that they are running in the last step (this is good for
- // transformations which can be merged)
- const declarative = this._isDeclarative;
- this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reset state anymore
- this._reseted = false;
- let converged = false; // Call initialise and the run function
- if (running || declarative) {
- this._initialise(running); // clear the transforms on this runner so they dont get added again and again
- this.transforms = new Matrix();
- converged = this._run(declarative ? dt : position);
- this.fire('step', this);
- } // correct the done flag here
- // declarative animations itself know when they converged
- this.done = this.done || converged && declarative;
- if (justFinished) {
- this.fire('finished', this);
- }
- return this;
- }
- /*
- Runner animation methods
- ========================
- Control how the animation plays
- */
- time(time) {
- if (time == null) {
- return this._time;
- }
- const dt = time - this._time;
- this.step(dt);
- return this;
- }
- timeline(timeline) {
- // check explicitly for undefined so we can set the timeline to null
- if (typeof timeline === 'undefined') return this._timeline;
- this._timeline = timeline;
- return this;
- }
- unschedule() {
- const timeline = this.timeline();
- timeline && timeline.unschedule(this);
- return this;
- } // Run each initialise function in the runner if required
- _initialise(running) {
- // If we aren't running, we shouldn't initialise when not declarative
- if (!running && !this._isDeclarative) return; // Loop through all of the initialisers
- for (let i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current initialiser
- const current = this._queue[i]; // Determine whether we need to initialise
- const needsIt = this._isDeclarative || !current.initialised && running;
- running = !current.finished; // Call the initialiser if we need to
- if (needsIt && running) {
- current.initialiser.call(this);
- current.initialised = true;
- }
- }
- } // Save a morpher to the morpher list so that we can retarget it later
- _rememberMorpher(method, morpher) {
- this._history[method] = {
- morpher: morpher,
- caller: this._queue[this._queue.length - 1]
- }; // We have to resume the timeline in case a controller
- // is already done without being ever run
- // This can happen when e.g. this is done:
- // anim = el.animate(new SVG.Spring)
- // and later
- // anim.move(...)
- if (this._isDeclarative) {
- const timeline = this.timeline();
- timeline && timeline.play();
- }
- } // Try to set the target for a morpher if the morpher exists, otherwise
- // Run each run function for the position or dt given
- _run(positionOrDt) {
- // Run all of the _queue directly
- let allfinished = true;
- for (let i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current function to run
- const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
- // flag for the sake of declarative _queue
- const converged = current.runner.call(this, positionOrDt);
- current.finished = current.finished || converged === true;
- allfinished = allfinished && current.finished;
- } // We report when all of the constructors are finished
- return allfinished;
- } // do nothing and return false
- _tryRetarget(method, target, extra) {
- if (this._history[method]) {
- // if the last method wasn't even initialised, throw it away
- if (!this._history[method].caller.initialised) {
- const index = this._queue.indexOf(this._history[method].caller);
- this._queue.splice(index, 1);
- return false;
- } // for the case of transformations, we use the special retarget function
- // which has access to the outer scope
- if (this._history[method].caller.retarget) {
- this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient
- } else {
- this._history[method].morpher.to(target);
- }
- this._history[method].caller.finished = false;
- const timeline = this.timeline();
- timeline && timeline.play();
- return true;
- }
- return false;
- }
- }
- Runner.id = 0;
- class FakeRunner {
- constructor(transforms = new Matrix(), id = -1, done = true) {
- this.transforms = transforms;
- this.id = id;
- this.done = done;
- }
- clearTransformsFromQueue() {}
- }
- extend([Runner, FakeRunner], {
- mergeWith(runner) {
- return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
- }
- }); // FakeRunner.emptyRunner = new FakeRunner()
- const lmultiply = (last, curr) => last.lmultiplyO(curr);
- const getRunnerTransform = runner => runner.transforms;
- function mergeTransforms() {
- // Find the matrix to apply to the element and apply it
- const runners = this._transformationRunners.runners;
- const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
- this.transform(netTransform);
- this._transformationRunners.merge();
- if (this._transformationRunners.length() === 1) {
- this._frameId = null;
- }
- }
- class RunnerArray {
- constructor() {
- this.runners = [];
- this.ids = [];
- }
- add(runner) {
- if (this.runners.includes(runner)) return;
- const id = runner.id + 1;
- this.runners.push(runner);
- this.ids.push(id);
- return this;
- }
- clearBefore(id) {
- const deleteCnt = this.ids.indexOf(id + 1) || 1;
- this.ids.splice(0, deleteCnt, 0);
- this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue());
- return this;
- }
- edit(id, newRunner) {
- const index = this.ids.indexOf(id + 1);
- this.ids.splice(index, 1, id + 1);
- this.runners.splice(index, 1, newRunner);
- return this;
- }
- getByID(id) {
- return this.runners[this.ids.indexOf(id + 1)];
- }
- length() {
- return this.ids.length;
- }
- merge() {
- let lastRunner = null;
- for (let i = 0; i < this.runners.length; ++i) {
- const runner = this.runners[i];
- const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline
- && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id));
- if (condition) {
- // the +1 happens in the function
- this.remove(runner.id);
- const newRunner = runner.mergeWith(lastRunner);
- this.edit(lastRunner.id, newRunner);
- lastRunner = newRunner;
- --i;
- } else {
- lastRunner = runner;
- }
- }
- return this;
- }
- remove(id) {
- const index = this.ids.indexOf(id + 1);
- this.ids.splice(index, 1);
- this.runners.splice(index, 1);
- return this;
- }
- }
- registerMethods({
- Element: {
- animate(duration, delay, when) {
- const o = Runner.sanitise(duration, delay, when);
- const timeline = this.timeline();
- return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when);
- },
- delay(by, when) {
- return this.animate(0, by, when);
- },
- // this function searches for all runners on the element and deletes the ones
- // which run before the current one. This is because absolute transformations
- // overwrite anything anyway so there is no need to waste time computing
- // other runners
- _clearTransformRunnersBefore(currentRunner) {
- this._transformationRunners.clearBefore(currentRunner.id);
- },
- _currentTransform(current) {
- return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations
- // on the same runner which execute before the current transformation are
- // taken into account
- .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix());
- },
- _addRunner(runner) {
- this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of
- // all Animator functions. That is why we use immediate here to execute
- // the merge right after all frames are run
- Animator.cancelImmediate(this._frameId);
- this._frameId = Animator.immediate(mergeTransforms.bind(this));
- },
- _prepareRunner() {
- if (this._frameId == null) {
- this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
- }
- }
- }
- }); // Will output the elements from array A that are not in the array B
- const difference = (a, b) => a.filter(x => !b.includes(x));
- extend(Runner, {
- attr(a, v) {
- return this.styleAttr('attr', a, v);
- },
- // Add animatable styles
- css(s, v) {
- return this.styleAttr('css', s, v);
- },
- styleAttr(type, nameOrAttrs, val) {
- if (typeof nameOrAttrs === 'string') {
- return this.styleAttr(type, {
- [nameOrAttrs]: val
- });
- }
- let attrs = nameOrAttrs;
- if (this._tryRetarget(type, attrs)) return this;
- let morpher = new Morphable(this._stepper).to(attrs);
- let keys = Object.keys(attrs);
- this.queue(function () {
- morpher = morpher.from(this.element()[type](keys));
- }, function (pos) {
- this.element()[type](morpher.at(pos).valueOf());
- return morpher.done();
- }, function (newToAttrs) {
- // Check if any new keys were added
- const newKeys = Object.keys(newToAttrs);
- const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher
- if (differences.length) {
- // Get the values
- const addedFromAttrs = this.element()[type](differences); // Get the already initialized values
- const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new
- Object.assign(oldFromAttrs, addedFromAttrs);
- morpher.from(oldFromAttrs);
- } // Get the object from the morpher
- const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes
- Object.assign(oldToAttrs, newToAttrs); // Change morpher target
- morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again
- keys = newKeys;
- attrs = newToAttrs;
- });
- this._rememberMorpher(type, morpher);
- return this;
- },
- zoom(level, point) {
- if (this._tryRetarget('zoom', level, point)) return this;
- let morpher = new Morphable(this._stepper).to(new SVGNumber(level));
- this.queue(function () {
- morpher = morpher.from(this.element().zoom());
- }, function (pos) {
- this.element().zoom(morpher.at(pos), point);
- return morpher.done();
- }, function (newLevel, newPoint) {
- point = newPoint;
- morpher.to(newLevel);
- });
- this._rememberMorpher('zoom', morpher);
- return this;
- },
- /**
- ** absolute transformations
- **/
- //
- // M v -----|-----(D M v = F v)------|-----> T v
- //
- // 1. define the final state (T) and decompose it (once)
- // t = [tx, ty, the, lam, sy, sx]
- // 2. on every frame: pull the current state of all previous transforms
- // (M - m can change)
- // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
- // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
- // - Note F(0) = M
- // - Note F(1) = T
- // 4. Now you get the delta matrix as a result: D = F * inv(M)
- transform(transforms, relative, affine) {
- // If we have a declarative function, we should retarget it if possible
- relative = transforms.relative || relative;
- if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
- return this;
- } // Parse the parameters
- const isMatrix = Matrix.isMatrixLike(transforms);
- affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morpher and set its type
- const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
- let origin;
- let element;
- let current;
- let currentAngle;
- let startTransform;
- function setup() {
- // make sure element and origin is defined
- element = element || this.element();
- origin = origin || getOrigin(transforms, element);
- startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations
- element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute
- if (!relative) {
- element._clearTransformRunnersBefore(this);
- }
- }
- function run(pos) {
- // clear all other transforms before this in case something is saved
- // on this runner. We are absolute. We dont need these!
- if (!relative) this.clearTransform();
- const {
- x,
- y
- } = new Point(origin).transform(element._currentTransform(this));
- let target = new Matrix({ ...transforms,
- origin: [x, y]
- });
- let start = this._isDeclarative && current ? current : startTransform;
- if (affine) {
- target = target.decompose(x, y);
- start = start.decompose(x, y); // Get the current and target angle as it was set
- const rTarget = target.rotate;
- const rCurrent = start.rotate; // Figure out the shortest path to rotate directly
- const possibilities = [rTarget - 360, rTarget, rTarget + 360];
- const distances = possibilities.map(a => Math.abs(a - rCurrent));
- const shortest = Math.min(...distances);
- const index = distances.indexOf(shortest);
- target.rotate = possibilities[index];
- }
- if (relative) {
- // we have to be careful here not to overwrite the rotation
- // with the rotate method of Matrix
- if (!isMatrix) {
- target.rotate = transforms.rotate || 0;
- }
- if (this._isDeclarative && currentAngle) {
- start.rotate = currentAngle;
- }
- }
- morpher.from(start);
- morpher.to(target);
- const affineParameters = morpher.at(pos);
- currentAngle = affineParameters.rotate;
- current = new Matrix(affineParameters);
- this.addTransform(current);
- element._addRunner(this);
- return morpher.done();
- }
- function retarget(newTransforms) {
- // only get a new origin if it changed since the last call
- if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) {
- origin = getOrigin(newTransforms, element);
- } // overwrite the old transformations with the new ones
- transforms = { ...newTransforms,
- origin
- };
- }
- this.queue(setup, run, retarget, true);
- this._isDeclarative && this._rememberMorpher('transform', morpher);
- return this;
- },
- // Animatable x-axis
- x(x, relative) {
- return this._queueNumber('x', x);
- },
- // Animatable y-axis
- y(y) {
- return this._queueNumber('y', y);
- },
- dx(x = 0) {
- return this._queueNumberDelta('x', x);
- },
- dy(y = 0) {
- return this._queueNumberDelta('y', y);
- },
- dmove(x, y) {
- return this.dx(x).dy(y);
- },
- _queueNumberDelta(method, to) {
- to = new SVGNumber(to); // Try to change the target if we have this method already registered
- if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation
- const morpher = new Morphable(this._stepper).to(to);
- let from = null;
- this.queue(function () {
- from = this.element()[method]();
- morpher.from(from);
- morpher.to(from + to);
- }, function (pos) {
- this.element()[method](morpher.at(pos));
- return morpher.done();
- }, function (newTo) {
- morpher.to(from + new SVGNumber(newTo));
- }); // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher);
- return this;
- },
- _queueObject(method, to) {
- // Try to change the target if we have this method already registered
- if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation
- const morpher = new Morphable(this._stepper).to(to);
- this.queue(function () {
- morpher.from(this.element()[method]());
- }, function (pos) {
- this.element()[method](morpher.at(pos));
- return morpher.done();
- }); // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher);
- return this;
- },
- _queueNumber(method, value) {
- return this._queueObject(method, new SVGNumber(value));
- },
- // Animatable center x-axis
- cx(x) {
- return this._queueNumber('cx', x);
- },
- // Animatable center y-axis
- cy(y) {
- return this._queueNumber('cy', y);
- },
- // Add animatable move
- move(x, y) {
- return this.x(x).y(y);
- },
- // Add animatable center
- center(x, y) {
- return this.cx(x).cy(y);
- },
- // Add animatable size
- size(width, height) {
- // animate bbox based size for all other elements
- let box;
- if (!width || !height) {
- box = this._element.bbox();
- }
- if (!width) {
- width = box.width / box.height * height;
- }
- if (!height) {
- height = box.height / box.width * width;
- }
- return this.width(width).height(height);
- },
- // Add animatable width
- width(width) {
- return this._queueNumber('width', width);
- },
- // Add animatable height
- height(height) {
- return this._queueNumber('height', height);
- },
- // Add animatable plot
- plot(a, b, c, d) {
- // Lines can be plotted with 4 arguments
- if (arguments.length === 4) {
- return this.plot([a, b, c, d]);
- }
- if (this._tryRetarget('plot', a)) return this;
- const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a);
- this.queue(function () {
- morpher.from(this._element.array());
- }, function (pos) {
- this._element.plot(morpher.at(pos));
- return morpher.done();
- });
- this._rememberMorpher('plot', morpher);
- return this;
- },
- // Add leading method
- leading(value) {
- return this._queueNumber('leading', value);
- },
- // Add animatable viewbox
- viewbox(x, y, width, height) {
- return this._queueObject('viewbox', new Box(x, y, width, height));
- },
- update(o) {
- if (typeof o !== 'object') {
- return this.update({
- offset: arguments[0],
- color: arguments[1],
- opacity: arguments[2]
- });
- }
- if (o.opacity != null) this.attr('stop-opacity', o.opacity);
- if (o.color != null) this.attr('stop-color', o.color);
- if (o.offset != null) this.attr('offset', o.offset);
- return this;
- }
- });
- extend(Runner, {
- rx,
- ry,
- from,
- to
- });
- register(Runner, 'Runner');
- class Svg extends Container {
- constructor(node, attrs = node) {
- super(nodeOrNew('svg', node), attrs);
- this.namespace();
- } // Creates and returns defs element
- defs() {
- if (!this.isRoot()) return this.root().defs();
- return adopt(this.node.querySelector('defs')) || this.put(new Defs());
- }
- isRoot() {
- return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment';
- } // Add namespaces
- namespace() {
- if (!this.isRoot()) return this.root().namespace();
- return this.attr({
- xmlns: svg,
- version: '1.1'
- }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns);
- }
- removeNamespace() {
- return this.attr({
- xmlns: null,
- version: null
- }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns);
- } // Check if this is a root svg
- // If not, call root() from this element
- root() {
- if (this.isRoot()) return this;
- return super.root();
- }
- }
- registerMethods({
- Container: {
- // Create nested svg document
- nested: wrapWithAttrCheck(function () {
- return this.put(new Svg());
- })
- }
- });
- register(Svg, 'Svg', true);
- class Symbol extends Container {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('symbol', node), attrs);
- }
- }
- registerMethods({
- Container: {
- symbol: wrapWithAttrCheck(function () {
- return this.put(new Symbol());
- })
- }
- });
- register(Symbol, 'Symbol');
- function plain(text) {
- // clear if build mode is disabled
- if (this._build === false) {
- this.clear();
- } // create text node
- this.node.appendChild(globals.document.createTextNode(text));
- return this;
- } // Get length of text element
- function length() {
- return this.node.getComputedTextLength();
- } // Move over x-axis
- // Text is moved by its bounding box
- // text-anchor does NOT matter
- function x$1(x, box = this.bbox()) {
- if (x == null) {
- return box.x;
- }
- return this.attr('x', this.attr('x') + x - box.x);
- } // Move over y-axis
- function y$1(y, box = this.bbox()) {
- if (y == null) {
- return box.y;
- }
- return this.attr('y', this.attr('y') + y - box.y);
- }
- function move$1(x, y, box = this.bbox()) {
- return this.x(x, box).y(y, box);
- } // Move center over x-axis
- function cx(x, box = this.bbox()) {
- if (x == null) {
- return box.cx;
- }
- return this.attr('x', this.attr('x') + x - box.cx);
- } // Move center over y-axis
- function cy(y, box = this.bbox()) {
- if (y == null) {
- return box.cy;
- }
- return this.attr('y', this.attr('y') + y - box.cy);
- }
- function center(x, y, box = this.bbox()) {
- return this.cx(x, box).cy(y, box);
- }
- function ax(x) {
- return this.attr('x', x);
- }
- function ay(y) {
- return this.attr('y', y);
- }
- function amove(x, y) {
- return this.ax(x).ay(y);
- } // Enable / disable build mode
- function build(build) {
- this._build = !!build;
- return this;
- }
- var textable = {
- __proto__: null,
- plain: plain,
- length: length,
- x: x$1,
- y: y$1,
- move: move$1,
- cx: cx,
- cy: cy,
- center: center,
- ax: ax,
- ay: ay,
- amove: amove,
- build: build
- };
- class Text extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('text', node), attrs);
- this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding
- this._rebuild = true; // enable automatic updating of dy values
- this._build = false; // disable build mode for adding multiple lines
- } // Set / get leading
- leading(value) {
- // act as getter
- if (value == null) {
- return this.dom.leading;
- } // act as setter
- this.dom.leading = new SVGNumber(value);
- return this.rebuild();
- } // Rebuild appearance type
- rebuild(rebuild) {
- // store new rebuild flag if given
- if (typeof rebuild === 'boolean') {
- this._rebuild = rebuild;
- } // define position of all lines
- if (this._rebuild) {
- const self = this;
- let blankLineOffset = 0;
- const leading = this.dom.leading;
- this.each(function (i) {
- const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
- const dy = leading * new SVGNumber(fontSize);
- if (this.dom.newLined) {
- this.attr('x', self.attr('x'));
- if (this.text() === '\n') {
- blankLineOffset += dy;
- } else {
- this.attr('dy', i ? dy + blankLineOffset : 0);
- blankLineOffset = 0;
- }
- }
- });
- this.fire('rebuild');
- }
- return this;
- } // overwrite method from parent to set data properly
- setData(o) {
- this.dom = o;
- this.dom.leading = new SVGNumber(o.leading || 1.3);
- return this;
- } // Set the text content
- text(text) {
- // act as getter
- if (text === undefined) {
- const children = this.node.childNodes;
- let firstLine = 0;
- text = '';
- for (let i = 0, len = children.length; i < len; ++i) {
- // skip textPaths - they are no lines
- if (children[i].nodeName === 'textPath') {
- if (i === 0) firstLine = 1;
- continue;
- } // add newline if its not the first child and newLined is set to true
- if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
- text += '\n';
- } // add content of this node
- text += children[i].textContent;
- }
- return text;
- } // remove existing content
- this.clear().build(true);
- if (typeof text === 'function') {
- // call block
- text.call(this, this);
- } else {
- // store text and make sure text is not blank
- text = (text + '').split('\n'); // build new lines
- for (let j = 0, jl = text.length; j < jl; j++) {
- this.newLine(text[j]);
- }
- } // disable build mode and rebuild lines
- return this.build(false).rebuild();
- }
- }
- extend(Text, textable);
- registerMethods({
- Container: {
- // Create text element
- text: wrapWithAttrCheck(function (text = '') {
- return this.put(new Text()).text(text);
- }),
- // Create plain text element
- plain: wrapWithAttrCheck(function (text = '') {
- return this.put(new Text()).plain(text);
- })
- }
- });
- register(Text, 'Text');
- class Tspan extends Shape {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('tspan', node), attrs);
- this._build = false; // disable build mode for adding multiple lines
- } // Shortcut dx
- dx(dx) {
- return this.attr('dx', dx);
- } // Shortcut dy
- dy(dy) {
- return this.attr('dy', dy);
- } // Create new line
- newLine() {
- // mark new line
- this.dom.newLined = true; // fetch parent
- const text = this.parent(); // early return in case we are not in a text element
- if (!(text instanceof Text)) {
- return this;
- }
- const i = text.index(this);
- const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
- const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position
- return this.dy(i ? dy : 0).attr('x', text.x());
- } // Set text content
- text(text) {
- if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '');
- if (typeof text === 'function') {
- this.clear().build(true);
- text.call(this, this);
- this.build(false);
- } else {
- this.plain(text);
- }
- return this;
- }
- }
- extend(Tspan, textable);
- registerMethods({
- Tspan: {
- tspan: wrapWithAttrCheck(function (text = '') {
- const tspan = new Tspan(); // clear if build mode is disabled
- if (!this._build) {
- this.clear();
- } // add new tspan
- return this.put(tspan).text(text);
- })
- },
- Text: {
- newLine: function (text = '') {
- return this.tspan(text).newLine();
- }
- }
- });
- register(Tspan, 'Tspan');
- class Circle extends Shape {
- constructor(node, attrs = node) {
- super(nodeOrNew('circle', node), attrs);
- }
- radius(r) {
- return this.attr('r', r);
- } // Radius x value
- rx(rx) {
- return this.attr('r', rx);
- } // Alias radius x value
- ry(ry) {
- return this.rx(ry);
- }
- size(size) {
- return this.radius(new SVGNumber(size).divide(2));
- }
- }
- extend(Circle, {
- x: x$3,
- y: y$3,
- cx: cx$1,
- cy: cy$1,
- width: width$2,
- height: height$2
- });
- registerMethods({
- Container: {
- // Create circle element
- circle: wrapWithAttrCheck(function (size = 0) {
- return this.put(new Circle()).size(size).move(0, 0);
- })
- }
- });
- register(Circle, 'Circle');
- class ClipPath extends Container {
- constructor(node, attrs = node) {
- super(nodeOrNew('clipPath', node), attrs);
- } // Unclip all clipped elements and remove itself
- remove() {
- // unclip all targets
- this.targets().forEach(function (el) {
- el.unclip();
- }); // remove clipPath from parent
- return super.remove();
- }
- targets() {
- return baseFind('svg [clip-path*=' + this.id() + ']');
- }
- }
- registerMethods({
- Container: {
- // Create clipping element
- clip: wrapWithAttrCheck(function () {
- return this.defs().put(new ClipPath());
- })
- },
- Element: {
- // Distribute clipPath to svg element
- clipper() {
- return this.reference('clip-path');
- },
- clipWith(element) {
- // use given clip or create a new one
- const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask
- return this.attr('clip-path', 'url(#' + clipper.id() + ')');
- },
- // Unclip element
- unclip() {
- return this.attr('clip-path', null);
- }
- }
- });
- register(ClipPath, 'ClipPath');
- class ForeignObject extends Element {
- constructor(node, attrs = node) {
- super(nodeOrNew('foreignObject', node), attrs);
- }
- }
- registerMethods({
- Container: {
- foreignObject: wrapWithAttrCheck(function (width, height) {
- return this.put(new ForeignObject()).size(width, height);
- })
- }
- });
- register(ForeignObject, 'ForeignObject');
- function dmove(dx, dy) {
- this.children().forEach((child, i) => {
- let bbox; // We have to wrap this for elements that dont have a bbox
- // e.g. title and other descriptive elements
- try {
- // Get the childs bbox
- bbox = child.bbox();
- } catch (e) {
- return;
- } // Get childs matrix
- const m = new Matrix(child); // Translate childs matrix by amount and
- // transform it back into parents space
- const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box
- const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element
- child.move(p.x, p.y);
- });
- return this;
- }
- function dx(dx) {
- return this.dmove(dx, 0);
- }
- function dy(dy) {
- return this.dmove(0, dy);
- }
- function height(height, box = this.bbox()) {
- if (height == null) return box.height;
- return this.size(box.width, height, box);
- }
- function move(x = 0, y = 0, box = this.bbox()) {
- const dx = x - box.x;
- const dy = y - box.y;
- return this.dmove(dx, dy);
- }
- function size(width, height, box = this.bbox()) {
- const p = proportionalSize(this, width, height, box);
- const scaleX = p.width / box.width;
- const scaleY = p.height / box.height;
- this.children().forEach((child, i) => {
- const o = new Point(box).transform(new Matrix(child).inverse());
- child.scale(scaleX, scaleY, o.x, o.y);
- });
- return this;
- }
- function width(width, box = this.bbox()) {
- if (width == null) return box.width;
- return this.size(width, box.height, box);
- }
- function x(x, box = this.bbox()) {
- if (x == null) return box.x;
- return this.move(x, box.y, box);
- }
- function y(y, box = this.bbox()) {
- if (y == null) return box.y;
- return this.move(box.x, y, box);
- }
- var containerGeometry = {
- __proto__: null,
- dmove: dmove,
- dx: dx,
- dy: dy,
- height: height,
- move: move,
- size: size,
- width: width,
- x: x,
- y: y
- };
- class G extends Container {
- constructor(node, attrs = node) {
- super(nodeOrNew('g', node), attrs);
- }
- }
- extend(G, containerGeometry);
- registerMethods({
- Container: {
- // Create a group element
- group: wrapWithAttrCheck(function () {
- return this.put(new G());
- })
- }
- });
- register(G, 'G');
- class A extends Container {
- constructor(node, attrs = node) {
- super(nodeOrNew('a', node), attrs);
- } // Link target attribute
- target(target) {
- return this.attr('target', target);
- } // Link url
- to(url) {
- return this.attr('href', url, xlink);
- }
- }
- extend(A, containerGeometry);
- registerMethods({
- Container: {
- // Create a hyperlink element
- link: wrapWithAttrCheck(function (url) {
- return this.put(new A()).to(url);
- })
- },
- Element: {
- unlink() {
- const link = this.linker();
- if (!link) return this;
- const parent = link.parent();
- if (!parent) {
- return this.remove();
- }
- const index = parent.index(link);
- parent.add(this, index);
- link.remove();
- return this;
- },
- linkTo(url) {
- // reuse old link if possible
- let link = this.linker();
- if (!link) {
- link = new A();
- this.wrap(link);
- }
- if (typeof url === 'function') {
- url.call(link, link);
- } else {
- link.to(url);
- }
- return this;
- },
- linker() {
- const link = this.parent();
- if (link && link.node.nodeName.toLowerCase() === 'a') {
- return link;
- }
- return null;
- }
- }
- });
- register(A, 'A');
- class Mask extends Container {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('mask', node), attrs);
- } // Unmask all masked elements and remove itself
- remove() {
- // unmask all targets
- this.targets().forEach(function (el) {
- el.unmask();
- }); // remove mask from parent
- return super.remove();
- }
- targets() {
- return baseFind('svg [mask*=' + this.id() + ']');
- }
- }
- registerMethods({
- Container: {
- mask: wrapWithAttrCheck(function () {
- return this.defs().put(new Mask());
- })
- },
- Element: {
- // Distribute mask to svg element
- masker() {
- return this.reference('mask');
- },
- maskWith(element) {
- // use given mask or create a new one
- const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask
- return this.attr('mask', 'url(#' + masker.id() + ')');
- },
- // Unmask element
- unmask() {
- return this.attr('mask', null);
- }
- }
- });
- register(Mask, 'Mask');
- class Stop extends Element {
- constructor(node, attrs = node) {
- super(nodeOrNew('stop', node), attrs);
- } // add color stops
- update(o) {
- if (typeof o === 'number' || o instanceof SVGNumber) {
- o = {
- offset: arguments[0],
- color: arguments[1],
- opacity: arguments[2]
- };
- } // set attributes
- if (o.opacity != null) this.attr('stop-opacity', o.opacity);
- if (o.color != null) this.attr('stop-color', o.color);
- if (o.offset != null) this.attr('offset', new SVGNumber(o.offset));
- return this;
- }
- }
- registerMethods({
- Gradient: {
- // Add a color stop
- stop: function (offset, color, opacity) {
- return this.put(new Stop()).update(offset, color, opacity);
- }
- }
- });
- register(Stop, 'Stop');
- function cssRule(selector, rule) {
- if (!selector) return '';
- if (!rule) return selector;
- let ret = selector + '{';
- for (const i in rule) {
- ret += unCamelCase(i) + ':' + rule[i] + ';';
- }
- ret += '}';
- return ret;
- }
- class Style extends Element {
- constructor(node, attrs = node) {
- super(nodeOrNew('style', node), attrs);
- }
- addText(w = '') {
- this.node.textContent += w;
- return this;
- }
- font(name, src, params = {}) {
- return this.rule('@font-face', {
- fontFamily: name,
- src: src,
- ...params
- });
- }
- rule(selector, obj) {
- return this.addText(cssRule(selector, obj));
- }
- }
- registerMethods('Dom', {
- style(selector, obj) {
- return this.put(new Style()).rule(selector, obj);
- },
- fontface(name, src, params) {
- return this.put(new Style()).font(name, src, params);
- }
- });
- register(Style, 'Style');
- class TextPath extends Text {
- // Initialize node
- constructor(node, attrs = node) {
- super(nodeOrNew('textPath', node), attrs);
- } // return the array of the path track element
- array() {
- const track = this.track();
- return track ? track.array() : null;
- } // Plot path if any
- plot(d) {
- const track = this.track();
- let pathArray = null;
- if (track) {
- pathArray = track.plot(d);
- }
- return d == null ? pathArray : this;
- } // Get the path element
- track() {
- return this.reference('href');
- }
- }
- registerMethods({
- Container: {
- textPath: wrapWithAttrCheck(function (text, path) {
- // Convert text to instance if needed
- if (!(text instanceof Text)) {
- text = this.text(text);
- }
- return text.path(path);
- })
- },
- Text: {
- // Create path for text to run on
- path: wrapWithAttrCheck(function (track, importNodes = true) {
- const textPath = new TextPath(); // if track is a path, reuse it
- if (!(track instanceof Path)) {
- // create path element
- track = this.defs().path(track);
- } // link textPath to path and add content
- textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath
- let node;
- if (importNodes) {
- while (node = this.node.firstChild) {
- textPath.node.appendChild(node);
- }
- } // add textPath element as child node and return textPath
- return this.put(textPath);
- }),
- // Get the textPath children
- textPath() {
- return this.findOne('textPath');
- }
- },
- Path: {
- // creates a textPath from this path
- text: wrapWithAttrCheck(function (text) {
- // Convert text to instance if needed
- if (!(text instanceof Text)) {
- text = new Text().addTo(this.parent()).text(text);
- } // Create textPath from text and path and return
- return text.path(this);
- }),
- targets() {
- return baseFind('svg textPath').filter(node => {
- return (node.attr('href') || '').includes(this.id());
- }); // Does not work in IE11. Use when IE support is dropped
- // return baseFind('svg textPath[*|href*=' + this.id() + ']')
- }
- }
- });
- TextPath.prototype.MorphArray = PathArray;
- register(TextPath, 'TextPath');
- class Use extends Shape {
- constructor(node, attrs = node) {
- super(nodeOrNew('use', node), attrs);
- } // Use element as a reference
- use(element, file) {
- // Set lined element
- return this.attr('href', (file || '') + '#' + element, xlink);
- }
- }
- registerMethods({
- Container: {
- // Create a use element
- use: wrapWithAttrCheck(function (element, file) {
- return this.put(new Use()).use(element, file);
- })
- }
- });
- register(Use, 'Use');
- /* Optional Modules */
- const SVG = makeInstance;
- extend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox'));
- extend([Line, Polyline, Polygon, Path], getMethodsFor('marker'));
- extend(Text, getMethodsFor('Text'));
- extend(Path, getMethodsFor('Path'));
- extend(Defs, getMethodsFor('Defs'));
- extend([Text, Tspan], getMethodsFor('Tspan'));
- extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'));
- extend(EventTarget, getMethodsFor('EventTarget'));
- extend(Dom, getMethodsFor('Dom'));
- extend(Element, getMethodsFor('Element'));
- extend(Shape, getMethodsFor('Shape'));
- extend([Container, Fragment], getMethodsFor('Container'));
- extend(Gradient, getMethodsFor('Gradient'));
- extend(Runner, getMethodsFor('Runner'));
- List.extend(getMethodNames());
- registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray, Point]);
- makeMorphable();
- exports.A = A;
- exports.Animator = Animator;
- exports.Array = SVGArray;
- exports.Box = Box;
- exports.Circle = Circle;
- exports.ClipPath = ClipPath;
- exports.Color = Color;
- exports.Container = Container;
- exports.Controller = Controller;
- exports.Defs = Defs;
- exports.Dom = Dom;
- exports.Ease = Ease;
- exports.Element = Element;
- exports.Ellipse = Ellipse;
- exports.EventTarget = EventTarget;
- exports.ForeignObject = ForeignObject;
- exports.Fragment = Fragment;
- exports.G = G;
- exports.Gradient = Gradient;
- exports.Image = Image;
- exports.Line = Line;
- exports.List = List;
- exports.Marker = Marker;
- exports.Mask = Mask;
- exports.Matrix = Matrix;
- exports.Morphable = Morphable;
- exports.NonMorphable = NonMorphable;
- exports.Number = SVGNumber;
- exports.ObjectBag = ObjectBag;
- exports.PID = PID;
- exports.Path = Path;
- exports.PathArray = PathArray;
- exports.Pattern = Pattern;
- exports.Point = Point;
- exports.PointArray = PointArray;
- exports.Polygon = Polygon;
- exports.Polyline = Polyline;
- exports.Queue = Queue;
- exports.Rect = Rect;
- exports.Runner = Runner;
- exports.SVG = SVG;
- exports.Shape = Shape;
- exports.Spring = Spring;
- exports.Stop = Stop;
- exports.Style = Style;
- exports.Svg = Svg;
- exports.Symbol = Symbol;
- exports.Text = Text;
- exports.TextPath = TextPath;
- exports.Timeline = Timeline;
- exports.TransformBag = TransformBag;
- exports.Tspan = Tspan;
- exports.Use = Use;
- exports.adopt = adopt;
- exports.assignNewId = assignNewId;
- exports.clearEvents = clearEvents;
- exports.create = create;
- exports.defaults = defaults;
- exports.dispatch = dispatch;
- exports.easing = easing;
- exports.eid = eid;
- exports.extend = extend;
- exports.find = baseFind;
- exports.getClass = getClass;
- exports.getEventTarget = getEventTarget;
- exports.getEvents = getEvents;
- exports.getWindow = getWindow;
- exports.makeInstance = makeInstance;
- exports.makeMorphable = makeMorphable;
- exports.mockAdopt = mockAdopt;
- exports.namespaces = namespaces;
- exports.nodeOrNew = nodeOrNew;
- exports.off = off;
- exports.on = on;
- exports.parser = parser;
- exports.regex = regex;
- exports.register = register;
- exports.registerMorphableType = registerMorphableType;
- exports.registerWindow = registerWindow;
- exports.restoreWindow = restoreWindow;
- exports.root = root;
- exports.saveWindow = saveWindow;
- exports.utils = utils;
- exports.windowEvents = windowEvents;
- exports.withWindow = withWindow;
- exports.wrapWithAttrCheck = wrapWithAttrCheck;
- //# sourceMappingURL=svg.node.js.map
|