| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175 |
- /*!
- * @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)
- */;
- var SVG = (function () {
- 'use strict';
- 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$1 = 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();
- var svgMembers = {
- __proto__: null,
- Morphable: Morphable,
- registerMorphableType: registerMorphableType,
- makeMorphable: makeMorphable,
- TransformBag: TransformBag,
- ObjectBag: ObjectBag,
- NonMorphable: NonMorphable,
- defaults: defaults,
- utils: utils,
- namespaces: namespaces,
- regex: regex,
- SVG: SVG$1,
- parser: parser,
- find: baseFind,
- getWindow: getWindow,
- registerWindow: registerWindow,
- restoreWindow: restoreWindow,
- saveWindow: saveWindow,
- withWindow: withWindow,
- Animator: Animator,
- Controller: Controller,
- Ease: Ease,
- PID: PID,
- Spring: Spring,
- easing: easing,
- Queue: Queue,
- Runner: Runner,
- Timeline: Timeline,
- Array: SVGArray,
- Box: Box,
- Color: Color,
- EventTarget: EventTarget,
- Matrix: Matrix,
- Number: SVGNumber,
- PathArray: PathArray,
- Point: Point,
- PointArray: PointArray,
- List: List,
- Circle: Circle,
- ClipPath: ClipPath,
- Container: Container,
- Defs: Defs,
- Dom: Dom,
- Element: Element,
- Ellipse: Ellipse,
- ForeignObject: ForeignObject,
- Fragment: Fragment,
- Gradient: Gradient,
- G: G,
- A: A,
- Image: Image,
- Line: Line,
- Marker: Marker,
- Mask: Mask,
- Path: Path,
- Pattern: Pattern,
- Polygon: Polygon,
- Polyline: Polyline,
- Rect: Rect,
- Shape: Shape,
- Stop: Stop,
- Style: Style,
- Svg: Svg,
- Symbol: Symbol,
- Text: Text,
- TextPath: TextPath,
- Tspan: Tspan,
- Use: Use,
- windowEvents: windowEvents,
- getEvents: getEvents,
- getEventTarget: getEventTarget,
- clearEvents: clearEvents,
- on: on,
- off: off,
- dispatch: dispatch,
- root: root,
- create: create,
- makeInstance: makeInstance,
- nodeOrNew: nodeOrNew,
- adopt: adopt,
- mockAdopt: mockAdopt,
- register: register,
- getClass: getClass,
- eid: eid,
- assignNewId: assignNewId,
- extend: extend,
- wrapWithAttrCheck: wrapWithAttrCheck
- };
- function SVG(element, isHTML) {
- return makeInstance(element, isHTML);
- }
- Object.assign(SVG, svgMembers);
- return SVG;
- })();
- //# sourceMappingURL=svg.js.map
|