svg.esm.js 174 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075
  1. /*!
  2. * @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG.
  3. * @version 3.2.0
  4. * https://svgjs.dev/
  5. *
  6. * @copyright Wout Fierens <wout@mick-wout.com>
  7. * @license MIT
  8. *
  9. * BUILT: Mon Jun 12 2023 10:34:51 GMT+0200 (Central European Summer Time)
  10. */;
  11. const methods$1 = {};
  12. const names = [];
  13. function registerMethods(name, m) {
  14. if (Array.isArray(name)) {
  15. for (const _name of name) {
  16. registerMethods(_name, m);
  17. }
  18. return;
  19. }
  20. if (typeof name === 'object') {
  21. for (const _name in name) {
  22. registerMethods(_name, name[_name]);
  23. }
  24. return;
  25. }
  26. addMethodNames(Object.getOwnPropertyNames(m));
  27. methods$1[name] = Object.assign(methods$1[name] || {}, m);
  28. }
  29. function getMethodsFor(name) {
  30. return methods$1[name] || {};
  31. }
  32. function getMethodNames() {
  33. return [...new Set(names)];
  34. }
  35. function addMethodNames(_names) {
  36. names.push(..._names);
  37. }
  38. // Map function
  39. function map(array, block) {
  40. let i;
  41. const il = array.length;
  42. const result = [];
  43. for (i = 0; i < il; i++) {
  44. result.push(block(array[i]));
  45. }
  46. return result;
  47. } // Filter function
  48. function filter(array, block) {
  49. let i;
  50. const il = array.length;
  51. const result = [];
  52. for (i = 0; i < il; i++) {
  53. if (block(array[i])) {
  54. result.push(array[i]);
  55. }
  56. }
  57. return result;
  58. } // Degrees to radians
  59. function radians(d) {
  60. return d % 360 * Math.PI / 180;
  61. } // Radians to degrees
  62. function degrees(r) {
  63. return r * 180 / Math.PI % 360;
  64. } // Convert dash-separated-string to camelCase
  65. function camelCase(s) {
  66. return s.toLowerCase().replace(/-(.)/g, function (m, g) {
  67. return g.toUpperCase();
  68. });
  69. } // Convert camel cased string to dash separated
  70. function unCamelCase(s) {
  71. return s.replace(/([A-Z])/g, function (m, g) {
  72. return '-' + g.toLowerCase();
  73. });
  74. } // Capitalize first letter of a string
  75. function capitalize(s) {
  76. return s.charAt(0).toUpperCase() + s.slice(1);
  77. } // Calculate proportional width and height values when necessary
  78. function proportionalSize(element, width, height, box) {
  79. if (width == null || height == null) {
  80. box = box || element.bbox();
  81. if (width == null) {
  82. width = box.width / box.height * height;
  83. } else if (height == null) {
  84. height = box.height / box.width * width;
  85. }
  86. }
  87. return {
  88. width: width,
  89. height: height
  90. };
  91. }
  92. /**
  93. * This function adds support for string origins.
  94. * It searches for an origin in o.origin o.ox and o.originX.
  95. * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50
  96. **/
  97. function getOrigin(o, element) {
  98. const origin = o.origin; // First check if origin is in ox or originX
  99. let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center';
  100. let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'; // Then check if origin was used and overwrite in that case
  101. if (origin != null) {
  102. [ox, oy] = Array.isArray(origin) ? origin : typeof origin === 'object' ? [origin.x, origin.y] : [origin, origin];
  103. } // Make sure to only call bbox when actually needed
  104. const condX = typeof ox === 'string';
  105. const condY = typeof oy === 'string';
  106. if (condX || condY) {
  107. const {
  108. height,
  109. width,
  110. x,
  111. y
  112. } = element.bbox(); // And only overwrite if string was passed for this specific axis
  113. if (condX) {
  114. ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2;
  115. }
  116. if (condY) {
  117. oy = oy.includes('top') ? y : oy.includes('bottom') ? y + height : y + height / 2;
  118. }
  119. } // Return the origin as it is if it wasn't a string
  120. return [ox, oy];
  121. }
  122. var utils = {
  123. __proto__: null,
  124. map: map,
  125. filter: filter,
  126. radians: radians,
  127. degrees: degrees,
  128. camelCase: camelCase,
  129. unCamelCase: unCamelCase,
  130. capitalize: capitalize,
  131. proportionalSize: proportionalSize,
  132. getOrigin: getOrigin
  133. };
  134. // Default namespaces
  135. const svg = 'http://www.w3.org/2000/svg';
  136. const html = 'http://www.w3.org/1999/xhtml';
  137. const xmlns = 'http://www.w3.org/2000/xmlns/';
  138. const xlink = 'http://www.w3.org/1999/xlink';
  139. const svgjs = 'http://svgjs.dev/svgjs';
  140. var namespaces = {
  141. __proto__: null,
  142. svg: svg,
  143. html: html,
  144. xmlns: xmlns,
  145. xlink: xlink,
  146. svgjs: svgjs
  147. };
  148. const globals = {
  149. window: typeof window === 'undefined' ? null : window,
  150. document: typeof document === 'undefined' ? null : document
  151. };
  152. function registerWindow(win = null, doc = null) {
  153. globals.window = win;
  154. globals.document = doc;
  155. }
  156. const save = {};
  157. function saveWindow() {
  158. save.window = globals.window;
  159. save.document = globals.document;
  160. }
  161. function restoreWindow() {
  162. globals.window = save.window;
  163. globals.document = save.document;
  164. }
  165. function withWindow(win, fn) {
  166. saveWindow();
  167. registerWindow(win, win.document);
  168. fn(win, win.document);
  169. restoreWindow();
  170. }
  171. function getWindow() {
  172. return globals.window;
  173. }
  174. class Base {// constructor (node/*, {extensions = []} */) {
  175. // // this.tags = []
  176. // //
  177. // // for (let extension of extensions) {
  178. // // extension.setup.call(this, node)
  179. // // this.tags.push(extension.name)
  180. // // }
  181. // }
  182. }
  183. const elements = {};
  184. const root = '___SYMBOL___ROOT___'; // Method for element creation
  185. function create(name, ns = svg) {
  186. // create element
  187. return globals.document.createElementNS(ns, name);
  188. }
  189. function makeInstance(element, isHTML = false) {
  190. if (element instanceof Base) return element;
  191. if (typeof element === 'object') {
  192. return adopter(element);
  193. }
  194. if (element == null) {
  195. return new elements[root]();
  196. }
  197. if (typeof element === 'string' && element.charAt(0) !== '<') {
  198. return adopter(globals.document.querySelector(element));
  199. } // Make sure, that HTML elements are created with the correct namespace
  200. const wrapper = isHTML ? globals.document.createElement('div') : create('svg');
  201. wrapper.innerHTML = element; // We can use firstChild here because we know,
  202. // that the first char is < and thus an element
  203. element = adopter(wrapper.firstChild); // make sure, that element doesn't have its wrapper attached
  204. wrapper.removeChild(wrapper.firstChild);
  205. return element;
  206. }
  207. function nodeOrNew(name, node) {
  208. return node && node.ownerDocument && node instanceof node.ownerDocument.defaultView.Node ? node : create(name);
  209. } // Adopt existing svg elements
  210. function adopt(node) {
  211. // check for presence of node
  212. if (!node) return null; // make sure a node isn't already adopted
  213. if (node.instance instanceof Base) return node.instance;
  214. if (node.nodeName === '#document-fragment') {
  215. return new elements.Fragment(node);
  216. } // initialize variables
  217. let className = capitalize(node.nodeName || 'Dom'); // Make sure that gradients are adopted correctly
  218. if (className === 'LinearGradient' || className === 'RadialGradient') {
  219. className = 'Gradient'; // Fallback to Dom if element is not known
  220. } else if (!elements[className]) {
  221. className = 'Dom';
  222. }
  223. return new elements[className](node);
  224. }
  225. let adopter = adopt;
  226. function mockAdopt(mock = adopt) {
  227. adopter = mock;
  228. }
  229. function register(element, name = element.name, asRoot = false) {
  230. elements[name] = element;
  231. if (asRoot) elements[root] = element;
  232. addMethodNames(Object.getOwnPropertyNames(element.prototype));
  233. return element;
  234. }
  235. function getClass(name) {
  236. return elements[name];
  237. } // Element id sequence
  238. let did = 1000; // Get next named element id
  239. function eid(name) {
  240. return 'Svgjs' + capitalize(name) + did++;
  241. } // Deep new id assignment
  242. function assignNewId(node) {
  243. // do the same for SVG child nodes as well
  244. for (let i = node.children.length - 1; i >= 0; i--) {
  245. assignNewId(node.children[i]);
  246. }
  247. if (node.id) {
  248. node.id = eid(node.nodeName);
  249. return node;
  250. }
  251. return node;
  252. } // Method for extending objects
  253. function extend(modules, methods) {
  254. let key, i;
  255. modules = Array.isArray(modules) ? modules : [modules];
  256. for (i = modules.length - 1; i >= 0; i--) {
  257. for (key in methods) {
  258. modules[i].prototype[key] = methods[key];
  259. }
  260. }
  261. }
  262. function wrapWithAttrCheck(fn) {
  263. return function (...args) {
  264. const o = args[args.length - 1];
  265. if (o && o.constructor === Object && !(o instanceof Array)) {
  266. return fn.apply(this, args.slice(0, -1)).attr(o);
  267. } else {
  268. return fn.apply(this, args);
  269. }
  270. };
  271. }
  272. function siblings() {
  273. return this.parent().children();
  274. } // Get the current position siblings
  275. function position() {
  276. return this.parent().index(this);
  277. } // Get the next element (will return null if there is none)
  278. function next() {
  279. return this.siblings()[this.position() + 1];
  280. } // Get the next element (will return null if there is none)
  281. function prev() {
  282. return this.siblings()[this.position() - 1];
  283. } // Send given element one step forward
  284. function forward() {
  285. const i = this.position();
  286. const p = this.parent(); // move node one step forward
  287. p.add(this.remove(), i + 1);
  288. return this;
  289. } // Send given element one step backward
  290. function backward() {
  291. const i = this.position();
  292. const p = this.parent();
  293. p.add(this.remove(), i ? i - 1 : 0);
  294. return this;
  295. } // Send given element all the way to the front
  296. function front() {
  297. const p = this.parent(); // Move node forward
  298. p.add(this.remove());
  299. return this;
  300. } // Send given element all the way to the back
  301. function back() {
  302. const p = this.parent(); // Move node back
  303. p.add(this.remove(), 0);
  304. return this;
  305. } // Inserts a given element before the targeted element
  306. function before(element) {
  307. element = makeInstance(element);
  308. element.remove();
  309. const i = this.position();
  310. this.parent().add(element, i);
  311. return this;
  312. } // Inserts a given element after the targeted element
  313. function after(element) {
  314. element = makeInstance(element);
  315. element.remove();
  316. const i = this.position();
  317. this.parent().add(element, i + 1);
  318. return this;
  319. }
  320. function insertBefore(element) {
  321. element = makeInstance(element);
  322. element.before(this);
  323. return this;
  324. }
  325. function insertAfter(element) {
  326. element = makeInstance(element);
  327. element.after(this);
  328. return this;
  329. }
  330. registerMethods('Dom', {
  331. siblings,
  332. position,
  333. next,
  334. prev,
  335. forward,
  336. backward,
  337. front,
  338. back,
  339. before,
  340. after,
  341. insertBefore,
  342. insertAfter
  343. });
  344. // Parse unit value
  345. const numberAndUnit = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i; // Parse hex value
  346. const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; // Parse rgb value
  347. const rgb = /rgb\((\d+),(\d+),(\d+)\)/; // Parse reference id
  348. const reference = /(#[a-z_][a-z0-9\-_]*)/i; // splits a transformation chain
  349. const transforms = /\)\s*,?\s*/; // Whitespace
  350. const whitespace = /\s/g; // Test hex value
  351. const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i; // Test rgb value
  352. const isRgb = /^rgb\(/; // Test for blank string
  353. const isBlank = /^(\s+)?$/; // Test for numeric string
  354. const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; // Test for image url
  355. const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i; // split at whitespace and comma
  356. const delimiter = /[\s,]+/; // Test for path letter
  357. const isPathLetter = /[MLHVCSQTAZ]/i;
  358. var regex = {
  359. __proto__: null,
  360. numberAndUnit: numberAndUnit,
  361. hex: hex,
  362. rgb: rgb,
  363. reference: reference,
  364. transforms: transforms,
  365. whitespace: whitespace,
  366. isHex: isHex,
  367. isRgb: isRgb,
  368. isBlank: isBlank,
  369. isNumber: isNumber,
  370. isImage: isImage,
  371. delimiter: delimiter,
  372. isPathLetter: isPathLetter
  373. };
  374. function classes() {
  375. const attr = this.attr('class');
  376. return attr == null ? [] : attr.trim().split(delimiter);
  377. } // Return true if class exists on the node, false otherwise
  378. function hasClass(name) {
  379. return this.classes().indexOf(name) !== -1;
  380. } // Add class to the node
  381. function addClass(name) {
  382. if (!this.hasClass(name)) {
  383. const array = this.classes();
  384. array.push(name);
  385. this.attr('class', array.join(' '));
  386. }
  387. return this;
  388. } // Remove class from the node
  389. function removeClass(name) {
  390. if (this.hasClass(name)) {
  391. this.attr('class', this.classes().filter(function (c) {
  392. return c !== name;
  393. }).join(' '));
  394. }
  395. return this;
  396. } // Toggle the presence of a class on the node
  397. function toggleClass(name) {
  398. return this.hasClass(name) ? this.removeClass(name) : this.addClass(name);
  399. }
  400. registerMethods('Dom', {
  401. classes,
  402. hasClass,
  403. addClass,
  404. removeClass,
  405. toggleClass
  406. });
  407. function css(style, val) {
  408. const ret = {};
  409. if (arguments.length === 0) {
  410. // get full style as object
  411. this.node.style.cssText.split(/\s*;\s*/).filter(function (el) {
  412. return !!el.length;
  413. }).forEach(function (el) {
  414. const t = el.split(/\s*:\s*/);
  415. ret[t[0]] = t[1];
  416. });
  417. return ret;
  418. }
  419. if (arguments.length < 2) {
  420. // get style properties as array
  421. if (Array.isArray(style)) {
  422. for (const name of style) {
  423. const cased = camelCase(name);
  424. ret[name] = this.node.style[cased];
  425. }
  426. return ret;
  427. } // get style for property
  428. if (typeof style === 'string') {
  429. return this.node.style[camelCase(style)];
  430. } // set styles in object
  431. if (typeof style === 'object') {
  432. for (const name in style) {
  433. // set empty string if null/undefined/'' was given
  434. this.node.style[camelCase(name)] = style[name] == null || isBlank.test(style[name]) ? '' : style[name];
  435. }
  436. }
  437. } // set style for property
  438. if (arguments.length === 2) {
  439. this.node.style[camelCase(style)] = val == null || isBlank.test(val) ? '' : val;
  440. }
  441. return this;
  442. } // Show element
  443. function show() {
  444. return this.css('display', '');
  445. } // Hide element
  446. function hide() {
  447. return this.css('display', 'none');
  448. } // Is element visible?
  449. function visible() {
  450. return this.css('display') !== 'none';
  451. }
  452. registerMethods('Dom', {
  453. css,
  454. show,
  455. hide,
  456. visible
  457. });
  458. function data(a, v, r) {
  459. if (a == null) {
  460. // get an object of attributes
  461. return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5)));
  462. } else if (a instanceof Array) {
  463. const data = {};
  464. for (const key of a) {
  465. data[key] = this.data(key);
  466. }
  467. return data;
  468. } else if (typeof a === 'object') {
  469. for (v in a) {
  470. this.data(v, a[v]);
  471. }
  472. } else if (arguments.length < 2) {
  473. try {
  474. return JSON.parse(this.attr('data-' + a));
  475. } catch (e) {
  476. return this.attr('data-' + a);
  477. }
  478. } else {
  479. this.attr('data-' + a, v === null ? null : r === true || typeof v === 'string' || typeof v === 'number' ? v : JSON.stringify(v));
  480. }
  481. return this;
  482. }
  483. registerMethods('Dom', {
  484. data
  485. });
  486. function remember(k, v) {
  487. // remember every item in an object individually
  488. if (typeof arguments[0] === 'object') {
  489. for (const key in k) {
  490. this.remember(key, k[key]);
  491. }
  492. } else if (arguments.length === 1) {
  493. // retrieve memory
  494. return this.memory()[k];
  495. } else {
  496. // store memory
  497. this.memory()[k] = v;
  498. }
  499. return this;
  500. } // Erase a given memory
  501. function forget() {
  502. if (arguments.length === 0) {
  503. this._memory = {};
  504. } else {
  505. for (let i = arguments.length - 1; i >= 0; i--) {
  506. delete this.memory()[arguments[i]];
  507. }
  508. }
  509. return this;
  510. } // This triggers creation of a new hidden class which is not performant
  511. // However, this function is not rarely used so it will not happen frequently
  512. // Return local memory object
  513. function memory() {
  514. return this._memory = this._memory || {};
  515. }
  516. registerMethods('Dom', {
  517. remember,
  518. forget,
  519. memory
  520. });
  521. function sixDigitHex(hex) {
  522. 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;
  523. }
  524. function componentHex(component) {
  525. const integer = Math.round(component);
  526. const bounded = Math.max(0, Math.min(255, integer));
  527. const hex = bounded.toString(16);
  528. return hex.length === 1 ? '0' + hex : hex;
  529. }
  530. function is(object, space) {
  531. for (let i = space.length; i--;) {
  532. if (object[space[i]] == null) {
  533. return false;
  534. }
  535. }
  536. return true;
  537. }
  538. function getParameters(a, b) {
  539. const params = is(a, 'rgb') ? {
  540. _a: a.r,
  541. _b: a.g,
  542. _c: a.b,
  543. _d: 0,
  544. space: 'rgb'
  545. } : is(a, 'xyz') ? {
  546. _a: a.x,
  547. _b: a.y,
  548. _c: a.z,
  549. _d: 0,
  550. space: 'xyz'
  551. } : is(a, 'hsl') ? {
  552. _a: a.h,
  553. _b: a.s,
  554. _c: a.l,
  555. _d: 0,
  556. space: 'hsl'
  557. } : is(a, 'lab') ? {
  558. _a: a.l,
  559. _b: a.a,
  560. _c: a.b,
  561. _d: 0,
  562. space: 'lab'
  563. } : is(a, 'lch') ? {
  564. _a: a.l,
  565. _b: a.c,
  566. _c: a.h,
  567. _d: 0,
  568. space: 'lch'
  569. } : is(a, 'cmyk') ? {
  570. _a: a.c,
  571. _b: a.m,
  572. _c: a.y,
  573. _d: a.k,
  574. space: 'cmyk'
  575. } : {
  576. _a: 0,
  577. _b: 0,
  578. _c: 0,
  579. space: 'rgb'
  580. };
  581. params.space = b || params.space;
  582. return params;
  583. }
  584. function cieSpace(space) {
  585. if (space === 'lab' || space === 'xyz' || space === 'lch') {
  586. return true;
  587. } else {
  588. return false;
  589. }
  590. }
  591. function hueToRgb(p, q, t) {
  592. if (t < 0) t += 1;
  593. if (t > 1) t -= 1;
  594. if (t < 1 / 6) return p + (q - p) * 6 * t;
  595. if (t < 1 / 2) return q;
  596. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  597. return p;
  598. }
  599. class Color {
  600. constructor(...inputs) {
  601. this.init(...inputs);
  602. } // Test if given value is a color
  603. static isColor(color) {
  604. return color && (color instanceof Color || this.isRgb(color) || this.test(color));
  605. } // Test if given value is an rgb object
  606. static isRgb(color) {
  607. return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number';
  608. }
  609. /*
  610. Generating random colors
  611. */
  612. static random(mode = 'vibrant', t, u) {
  613. // Get the math modules
  614. const {
  615. random,
  616. round,
  617. sin,
  618. PI: pi
  619. } = Math; // Run the correct generator
  620. if (mode === 'vibrant') {
  621. const l = (81 - 57) * random() + 57;
  622. const c = (83 - 45) * random() + 45;
  623. const h = 360 * random();
  624. const color = new Color(l, c, h, 'lch');
  625. return color;
  626. } else if (mode === 'sine') {
  627. t = t == null ? random() : t;
  628. const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150);
  629. const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200);
  630. const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150);
  631. const color = new Color(r, g, b);
  632. return color;
  633. } else if (mode === 'pastel') {
  634. const l = (94 - 86) * random() + 86;
  635. const c = (26 - 9) * random() + 9;
  636. const h = 360 * random();
  637. const color = new Color(l, c, h, 'lch');
  638. return color;
  639. } else if (mode === 'dark') {
  640. const l = 10 + 10 * random();
  641. const c = (125 - 75) * random() + 86;
  642. const h = 360 * random();
  643. const color = new Color(l, c, h, 'lch');
  644. return color;
  645. } else if (mode === 'rgb') {
  646. const r = 255 * random();
  647. const g = 255 * random();
  648. const b = 255 * random();
  649. const color = new Color(r, g, b);
  650. return color;
  651. } else if (mode === 'lab') {
  652. const l = 100 * random();
  653. const a = 256 * random() - 128;
  654. const b = 256 * random() - 128;
  655. const color = new Color(l, a, b, 'lab');
  656. return color;
  657. } else if (mode === 'grey') {
  658. const grey = 255 * random();
  659. const color = new Color(grey, grey, grey);
  660. return color;
  661. } else {
  662. throw new Error('Unsupported random color mode');
  663. }
  664. } // Test if given value is a color string
  665. static test(color) {
  666. return typeof color === 'string' && (isHex.test(color) || isRgb.test(color));
  667. }
  668. cmyk() {
  669. // Get the rgb values for the current color
  670. const {
  671. _a,
  672. _b,
  673. _c
  674. } = this.rgb();
  675. const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format
  676. const k = Math.min(1 - r, 1 - g, 1 - b);
  677. if (k === 1) {
  678. // Catch the black case
  679. return new Color(0, 0, 0, 1, 'cmyk');
  680. }
  681. const c = (1 - r - k) / (1 - k);
  682. const m = (1 - g - k) / (1 - k);
  683. const y = (1 - b - k) / (1 - k); // Construct the new color
  684. const color = new Color(c, m, y, k, 'cmyk');
  685. return color;
  686. }
  687. hsl() {
  688. // Get the rgb values
  689. const {
  690. _a,
  691. _b,
  692. _c
  693. } = this.rgb();
  694. const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness
  695. const max = Math.max(r, g, b);
  696. const min = Math.min(r, g, b);
  697. const l = (max + min) / 2; // If the r, g, v values are identical then we are grey
  698. const isGrey = max === min; // Calculate the hue and saturation
  699. const delta = max - min;
  700. const s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
  701. 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
  702. const color = new Color(360 * h, 100 * s, 100 * l, 'hsl');
  703. return color;
  704. }
  705. init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') {
  706. // This catches the case when a falsy value is passed like ''
  707. a = !a ? 0 : a; // Reset all values in case the init function is rerun with new color space
  708. if (this.space) {
  709. for (const component in this.space) {
  710. delete this[this.space[component]];
  711. }
  712. }
  713. if (typeof a === 'number') {
  714. // Allow for the case that we don't need d...
  715. space = typeof d === 'string' ? d : space;
  716. d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color
  717. Object.assign(this, {
  718. _a: a,
  719. _b: b,
  720. _c: c,
  721. _d: d,
  722. space
  723. }); // If the user gave us an array, make the color from it
  724. } else if (a instanceof Array) {
  725. this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb';
  726. Object.assign(this, {
  727. _a: a[0],
  728. _b: a[1],
  729. _c: a[2],
  730. _d: a[3] || 0
  731. });
  732. } else if (a instanceof Object) {
  733. // Set the object up and assign its values directly
  734. const values = getParameters(a, b);
  735. Object.assign(this, values);
  736. } else if (typeof a === 'string') {
  737. if (isRgb.test(a)) {
  738. const noWhitespace = a.replace(whitespace, '');
  739. const [_a, _b, _c] = rgb.exec(noWhitespace).slice(1, 4).map(v => parseInt(v));
  740. Object.assign(this, {
  741. _a,
  742. _b,
  743. _c,
  744. _d: 0,
  745. space: 'rgb'
  746. });
  747. } else if (isHex.test(a)) {
  748. const hexParse = v => parseInt(v, 16);
  749. const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse);
  750. Object.assign(this, {
  751. _a,
  752. _b,
  753. _c,
  754. _d: 0,
  755. space: 'rgb'
  756. });
  757. } else throw Error('Unsupported string format, can\'t construct Color');
  758. } // Now add the components as a convenience
  759. const {
  760. _a,
  761. _b,
  762. _c,
  763. _d
  764. } = this;
  765. const components = this.space === 'rgb' ? {
  766. r: _a,
  767. g: _b,
  768. b: _c
  769. } : this.space === 'xyz' ? {
  770. x: _a,
  771. y: _b,
  772. z: _c
  773. } : this.space === 'hsl' ? {
  774. h: _a,
  775. s: _b,
  776. l: _c
  777. } : this.space === 'lab' ? {
  778. l: _a,
  779. a: _b,
  780. b: _c
  781. } : this.space === 'lch' ? {
  782. l: _a,
  783. c: _b,
  784. h: _c
  785. } : this.space === 'cmyk' ? {
  786. c: _a,
  787. m: _b,
  788. y: _c,
  789. k: _d
  790. } : {};
  791. Object.assign(this, components);
  792. }
  793. lab() {
  794. // Get the xyz color
  795. const {
  796. x,
  797. y,
  798. z
  799. } = this.xyz(); // Get the lab components
  800. const l = 116 * y - 16;
  801. const a = 500 * (x - y);
  802. const b = 200 * (y - z); // Construct and return a new color
  803. const color = new Color(l, a, b, 'lab');
  804. return color;
  805. }
  806. lch() {
  807. // Get the lab color directly
  808. const {
  809. l,
  810. a,
  811. b
  812. } = this.lab(); // Get the chromaticity and the hue using polar coordinates
  813. const c = Math.sqrt(a ** 2 + b ** 2);
  814. let h = 180 * Math.atan2(b, a) / Math.PI;
  815. if (h < 0) {
  816. h *= -1;
  817. h = 360 - h;
  818. } // Make a new color and return it
  819. const color = new Color(l, c, h, 'lch');
  820. return color;
  821. }
  822. /*
  823. Conversion Methods
  824. */
  825. rgb() {
  826. if (this.space === 'rgb') {
  827. return this;
  828. } else if (cieSpace(this.space)) {
  829. // Convert to the xyz color space
  830. let {
  831. x,
  832. y,
  833. z
  834. } = this;
  835. if (this.space === 'lab' || this.space === 'lch') {
  836. // Get the values in the lab space
  837. let {
  838. l,
  839. a,
  840. b
  841. } = this;
  842. if (this.space === 'lch') {
  843. const {
  844. c,
  845. h
  846. } = this;
  847. const dToR = Math.PI / 180;
  848. a = c * Math.cos(dToR * h);
  849. b = c * Math.sin(dToR * h);
  850. } // Undo the nonlinear function
  851. const yL = (l + 16) / 116;
  852. const xL = a / 500 + yL;
  853. const zL = yL - b / 200; // Get the xyz values
  854. const ct = 16 / 116;
  855. const mx = 0.008856;
  856. const nm = 7.787;
  857. x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm);
  858. y = 1.00000 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm);
  859. z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm);
  860. } // Convert xyz to unbounded rgb values
  861. const rU = x * 3.2406 + y * -1.5372 + z * -0.4986;
  862. const gU = x * -0.9689 + y * 1.8758 + z * 0.0415;
  863. const bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values
  864. const pow = Math.pow;
  865. const bd = 0.0031308;
  866. const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU;
  867. const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU;
  868. const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color
  869. const color = new Color(255 * r, 255 * g, 255 * b);
  870. return color;
  871. } else if (this.space === 'hsl') {
  872. // https://bgrins.github.io/TinyColor/docs/tinycolor.html
  873. // Get the current hsl values
  874. let {
  875. h,
  876. s,
  877. l
  878. } = this;
  879. h /= 360;
  880. s /= 100;
  881. l /= 100; // If we are grey, then just make the color directly
  882. if (s === 0) {
  883. l *= 255;
  884. const color = new Color(l, l, l);
  885. return color;
  886. } // TODO I have no idea what this does :D If you figure it out, tell me!
  887. const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  888. const p = 2 * l - q; // Get the rgb values
  889. const r = 255 * hueToRgb(p, q, h + 1 / 3);
  890. const g = 255 * hueToRgb(p, q, h);
  891. const b = 255 * hueToRgb(p, q, h - 1 / 3); // Make a new color
  892. const color = new Color(r, g, b);
  893. return color;
  894. } else if (this.space === 'cmyk') {
  895. // https://gist.github.com/felipesabino/5066336
  896. // Get the normalised cmyk values
  897. const {
  898. c,
  899. m,
  900. y,
  901. k
  902. } = this; // Get the rgb values
  903. const r = 255 * (1 - Math.min(1, c * (1 - k) + k));
  904. const g = 255 * (1 - Math.min(1, m * (1 - k) + k));
  905. const b = 255 * (1 - Math.min(1, y * (1 - k) + k)); // Form the color and return it
  906. const color = new Color(r, g, b);
  907. return color;
  908. } else {
  909. return this;
  910. }
  911. }
  912. toArray() {
  913. const {
  914. _a,
  915. _b,
  916. _c,
  917. _d,
  918. space
  919. } = this;
  920. return [_a, _b, _c, _d, space];
  921. }
  922. toHex() {
  923. const [r, g, b] = this._clamped().map(componentHex);
  924. return `#${r}${g}${b}`;
  925. }
  926. toRgb() {
  927. const [rV, gV, bV] = this._clamped();
  928. const string = `rgb(${rV},${gV},${bV})`;
  929. return string;
  930. }
  931. toString() {
  932. return this.toHex();
  933. }
  934. xyz() {
  935. // Normalise the red, green and blue values
  936. const {
  937. _a: r255,
  938. _b: g255,
  939. _c: b255
  940. } = this.rgb();
  941. const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space
  942. const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
  943. const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
  944. 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
  945. const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047;
  946. const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000;
  947. const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding
  948. const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116;
  949. const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116;
  950. const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color
  951. const color = new Color(x, y, z, 'xyz');
  952. return color;
  953. }
  954. /*
  955. Input and Output methods
  956. */
  957. _clamped() {
  958. const {
  959. _a,
  960. _b,
  961. _c
  962. } = this.rgb();
  963. const {
  964. max,
  965. min,
  966. round
  967. } = Math;
  968. const format = v => max(0, min(round(v), 255));
  969. return [_a, _b, _c].map(format);
  970. }
  971. /*
  972. Constructing colors
  973. */
  974. }
  975. class Point {
  976. // Initialize
  977. constructor(...args) {
  978. this.init(...args);
  979. } // Clone point
  980. clone() {
  981. return new Point(this);
  982. }
  983. init(x, y) {
  984. const base = {
  985. x: 0,
  986. y: 0
  987. }; // ensure source as object
  988. const source = Array.isArray(x) ? {
  989. x: x[0],
  990. y: x[1]
  991. } : typeof x === 'object' ? {
  992. x: x.x,
  993. y: x.y
  994. } : {
  995. x: x,
  996. y: y
  997. }; // merge source
  998. this.x = source.x == null ? base.x : source.x;
  999. this.y = source.y == null ? base.y : source.y;
  1000. return this;
  1001. }
  1002. toArray() {
  1003. return [this.x, this.y];
  1004. }
  1005. transform(m) {
  1006. return this.clone().transformO(m);
  1007. } // Transform point with matrix
  1008. transformO(m) {
  1009. if (!Matrix.isMatrixLike(m)) {
  1010. m = new Matrix(m);
  1011. }
  1012. const {
  1013. x,
  1014. y
  1015. } = this; // Perform the matrix multiplication
  1016. this.x = m.a * x + m.c * y + m.e;
  1017. this.y = m.b * x + m.d * y + m.f;
  1018. return this;
  1019. }
  1020. }
  1021. function point(x, y) {
  1022. return new Point(x, y).transformO(this.screenCTM().inverseO());
  1023. }
  1024. function closeEnough(a, b, threshold) {
  1025. return Math.abs(b - a) < (threshold || 1e-6);
  1026. }
  1027. class Matrix {
  1028. constructor(...args) {
  1029. this.init(...args);
  1030. }
  1031. static formatTransforms(o) {
  1032. // Get all of the parameters required to form the matrix
  1033. const flipBoth = o.flip === 'both' || o.flip === true;
  1034. const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1;
  1035. const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1;
  1036. const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0;
  1037. const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0;
  1038. const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX;
  1039. const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY;
  1040. const shear = o.shear || 0;
  1041. const theta = o.rotate || o.theta || 0;
  1042. const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY);
  1043. const ox = origin.x;
  1044. 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
  1045. const position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN);
  1046. const px = position.x;
  1047. const py = position.y;
  1048. const translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY);
  1049. const tx = translate.x;
  1050. const ty = translate.y;
  1051. const relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY);
  1052. const rx = relative.x;
  1053. const ry = relative.y; // Populate all of the values
  1054. return {
  1055. scaleX,
  1056. scaleY,
  1057. skewX,
  1058. skewY,
  1059. shear,
  1060. theta,
  1061. rx,
  1062. ry,
  1063. tx,
  1064. ty,
  1065. ox,
  1066. oy,
  1067. px,
  1068. py
  1069. };
  1070. }
  1071. static fromArray(a) {
  1072. return {
  1073. a: a[0],
  1074. b: a[1],
  1075. c: a[2],
  1076. d: a[3],
  1077. e: a[4],
  1078. f: a[5]
  1079. };
  1080. }
  1081. static isMatrixLike(o) {
  1082. return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null;
  1083. } // left matrix, right matrix, target matrix which is overwritten
  1084. static matrixMultiply(l, r, o) {
  1085. // Work out the product directly
  1086. const a = l.a * r.a + l.c * r.b;
  1087. const b = l.b * r.a + l.d * r.b;
  1088. const c = l.a * r.c + l.c * r.d;
  1089. const d = l.b * r.c + l.d * r.d;
  1090. const e = l.e + l.a * r.e + l.c * r.f;
  1091. 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
  1092. o.a = a;
  1093. o.b = b;
  1094. o.c = c;
  1095. o.d = d;
  1096. o.e = e;
  1097. o.f = f;
  1098. return o;
  1099. }
  1100. around(cx, cy, matrix) {
  1101. return this.clone().aroundO(cx, cy, matrix);
  1102. } // Transform around a center point
  1103. aroundO(cx, cy, matrix) {
  1104. const dx = cx || 0;
  1105. const dy = cy || 0;
  1106. return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy);
  1107. } // Clones this matrix
  1108. clone() {
  1109. return new Matrix(this);
  1110. } // Decomposes this matrix into its affine parameters
  1111. decompose(cx = 0, cy = 0) {
  1112. // Get the parameters from the matrix
  1113. const a = this.a;
  1114. const b = this.b;
  1115. const c = this.c;
  1116. const d = this.d;
  1117. const e = this.e;
  1118. const f = this.f; // Figure out if the winding direction is clockwise or counterclockwise
  1119. const determinant = a * d - b * c;
  1120. const ccw = determinant > 0 ? 1 : -1; // Since we only shear in x, we can use the x basis to get the x scale
  1121. // and the rotation of the resulting matrix
  1122. const sx = ccw * Math.sqrt(a * a + b * b);
  1123. const thetaRad = Math.atan2(ccw * b, ccw * a);
  1124. const theta = 180 / Math.PI * thetaRad;
  1125. const ct = Math.cos(thetaRad);
  1126. const st = Math.sin(thetaRad); // We can then solve the y basis vector simultaneously to get the other
  1127. // two affine parameters directly from these parameters
  1128. const lam = (a * c + b * d) / determinant;
  1129. const sy = c * sx / (lam * a - b) || d * sx / (lam * b + a); // Use the translations
  1130. const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy);
  1131. const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy); // Construct the decomposition and return it
  1132. return {
  1133. // Return the affine parameters
  1134. scaleX: sx,
  1135. scaleY: sy,
  1136. shear: lam,
  1137. rotate: theta,
  1138. translateX: tx,
  1139. translateY: ty,
  1140. originX: cx,
  1141. originY: cy,
  1142. // Return the matrix parameters
  1143. a: this.a,
  1144. b: this.b,
  1145. c: this.c,
  1146. d: this.d,
  1147. e: this.e,
  1148. f: this.f
  1149. };
  1150. } // Check if two matrices are equal
  1151. equals(other) {
  1152. if (other === this) return true;
  1153. const comp = new Matrix(other);
  1154. 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);
  1155. } // Flip matrix on x or y, at a given offset
  1156. flip(axis, around) {
  1157. return this.clone().flipO(axis, around);
  1158. }
  1159. flipO(axis, around) {
  1160. 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
  1161. } // Initialize
  1162. init(source) {
  1163. const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]); // ensure source as object
  1164. 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
  1165. this.a = source.a != null ? source.a : base.a;
  1166. this.b = source.b != null ? source.b : base.b;
  1167. this.c = source.c != null ? source.c : base.c;
  1168. this.d = source.d != null ? source.d : base.d;
  1169. this.e = source.e != null ? source.e : base.e;
  1170. this.f = source.f != null ? source.f : base.f;
  1171. return this;
  1172. }
  1173. inverse() {
  1174. return this.clone().inverseO();
  1175. } // Inverses matrix
  1176. inverseO() {
  1177. // Get the current parameters out of the matrix
  1178. const a = this.a;
  1179. const b = this.b;
  1180. const c = this.c;
  1181. const d = this.d;
  1182. const e = this.e;
  1183. const f = this.f; // Invert the 2x2 matrix in the top left
  1184. const det = a * d - b * c;
  1185. if (!det) throw new Error('Cannot invert ' + this); // Calculate the top 2x2 matrix
  1186. const na = d / det;
  1187. const nb = -b / det;
  1188. const nc = -c / det;
  1189. const nd = a / det; // Apply the inverted matrix to the top right
  1190. const ne = -(na * e + nc * f);
  1191. const nf = -(nb * e + nd * f); // Construct the inverted matrix
  1192. this.a = na;
  1193. this.b = nb;
  1194. this.c = nc;
  1195. this.d = nd;
  1196. this.e = ne;
  1197. this.f = nf;
  1198. return this;
  1199. }
  1200. lmultiply(matrix) {
  1201. return this.clone().lmultiplyO(matrix);
  1202. }
  1203. lmultiplyO(matrix) {
  1204. const r = this;
  1205. const l = matrix instanceof Matrix ? matrix : new Matrix(matrix);
  1206. return Matrix.matrixMultiply(l, r, this);
  1207. } // Left multiplies by the given matrix
  1208. multiply(matrix) {
  1209. return this.clone().multiplyO(matrix);
  1210. }
  1211. multiplyO(matrix) {
  1212. // Get the matrices
  1213. const l = this;
  1214. const r = matrix instanceof Matrix ? matrix : new Matrix(matrix);
  1215. return Matrix.matrixMultiply(l, r, this);
  1216. } // Rotate matrix
  1217. rotate(r, cx, cy) {
  1218. return this.clone().rotateO(r, cx, cy);
  1219. }
  1220. rotateO(r, cx = 0, cy = 0) {
  1221. // Convert degrees to radians
  1222. r = radians(r);
  1223. const cos = Math.cos(r);
  1224. const sin = Math.sin(r);
  1225. const {
  1226. a,
  1227. b,
  1228. c,
  1229. d,
  1230. e,
  1231. f
  1232. } = this;
  1233. this.a = a * cos - b * sin;
  1234. this.b = b * cos + a * sin;
  1235. this.c = c * cos - d * sin;
  1236. this.d = d * cos + c * sin;
  1237. this.e = e * cos - f * sin + cy * sin - cx * cos + cx;
  1238. this.f = f * cos + e * sin - cx * sin - cy * cos + cy;
  1239. return this;
  1240. } // Scale matrix
  1241. scale(x, y, cx, cy) {
  1242. return this.clone().scaleO(...arguments);
  1243. }
  1244. scaleO(x, y = x, cx = 0, cy = 0) {
  1245. // Support uniform scaling
  1246. if (arguments.length === 3) {
  1247. cy = cx;
  1248. cx = y;
  1249. y = x;
  1250. }
  1251. const {
  1252. a,
  1253. b,
  1254. c,
  1255. d,
  1256. e,
  1257. f
  1258. } = this;
  1259. this.a = a * x;
  1260. this.b = b * y;
  1261. this.c = c * x;
  1262. this.d = d * y;
  1263. this.e = e * x - cx * x + cx;
  1264. this.f = f * y - cy * y + cy;
  1265. return this;
  1266. } // Shear matrix
  1267. shear(a, cx, cy) {
  1268. return this.clone().shearO(a, cx, cy);
  1269. }
  1270. shearO(lx, cx = 0, cy = 0) {
  1271. const {
  1272. a,
  1273. b,
  1274. c,
  1275. d,
  1276. e,
  1277. f
  1278. } = this;
  1279. this.a = a + b * lx;
  1280. this.c = c + d * lx;
  1281. this.e = e + f * lx - cy * lx;
  1282. return this;
  1283. } // Skew Matrix
  1284. skew(x, y, cx, cy) {
  1285. return this.clone().skewO(...arguments);
  1286. }
  1287. skewO(x, y = x, cx = 0, cy = 0) {
  1288. // support uniformal skew
  1289. if (arguments.length === 3) {
  1290. cy = cx;
  1291. cx = y;
  1292. y = x;
  1293. } // Convert degrees to radians
  1294. x = radians(x);
  1295. y = radians(y);
  1296. const lx = Math.tan(x);
  1297. const ly = Math.tan(y);
  1298. const {
  1299. a,
  1300. b,
  1301. c,
  1302. d,
  1303. e,
  1304. f
  1305. } = this;
  1306. this.a = a + b * lx;
  1307. this.b = b + a * ly;
  1308. this.c = c + d * lx;
  1309. this.d = d + c * ly;
  1310. this.e = e + f * lx - cy * lx;
  1311. this.f = f + e * ly - cx * ly;
  1312. return this;
  1313. } // SkewX
  1314. skewX(x, cx, cy) {
  1315. return this.skew(x, 0, cx, cy);
  1316. } // SkewY
  1317. skewY(y, cx, cy) {
  1318. return this.skew(0, y, cx, cy);
  1319. }
  1320. toArray() {
  1321. return [this.a, this.b, this.c, this.d, this.e, this.f];
  1322. } // Convert matrix to string
  1323. toString() {
  1324. return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')';
  1325. } // Transform a matrix into another matrix by manipulating the space
  1326. transform(o) {
  1327. // Check if o is a matrix and then left multiply it directly
  1328. if (Matrix.isMatrixLike(o)) {
  1329. const matrix = new Matrix(o);
  1330. return matrix.multiplyO(this);
  1331. } // Get the proposed transformations and the current transformations
  1332. const t = Matrix.formatTransforms(o);
  1333. const current = this;
  1334. const {
  1335. x: ox,
  1336. y: oy
  1337. } = new Point(t.ox, t.oy).transform(current); // Construct the resulting matrix
  1338. 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
  1339. if (isFinite(t.px) || isFinite(t.py)) {
  1340. const origin = new Point(ox, oy).transform(transformer); // TODO: Replace t.px with isFinite(t.px)
  1341. // Doesn't work because t.px is also 0 if it wasn't passed
  1342. const dx = isFinite(t.px) ? t.px - origin.x : 0;
  1343. const dy = isFinite(t.py) ? t.py - origin.y : 0;
  1344. transformer.translateO(dx, dy);
  1345. } // Translate now after positioning
  1346. transformer.translateO(t.tx, t.ty);
  1347. return transformer;
  1348. } // Translate matrix
  1349. translate(x, y) {
  1350. return this.clone().translateO(x, y);
  1351. }
  1352. translateO(x, y) {
  1353. this.e += x || 0;
  1354. this.f += y || 0;
  1355. return this;
  1356. }
  1357. valueOf() {
  1358. return {
  1359. a: this.a,
  1360. b: this.b,
  1361. c: this.c,
  1362. d: this.d,
  1363. e: this.e,
  1364. f: this.f
  1365. };
  1366. }
  1367. }
  1368. function ctm() {
  1369. return new Matrix(this.node.getCTM());
  1370. }
  1371. function screenCTM() {
  1372. /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
  1373. This is needed because FF does not return the transformation matrix
  1374. for the inner coordinate system when getScreenCTM() is called on nested svgs.
  1375. However all other Browsers do that */
  1376. if (typeof this.isRoot === 'function' && !this.isRoot()) {
  1377. const rect = this.rect(1, 1);
  1378. const m = rect.node.getScreenCTM();
  1379. rect.remove();
  1380. return new Matrix(m);
  1381. }
  1382. return new Matrix(this.node.getScreenCTM());
  1383. }
  1384. register(Matrix, 'Matrix');
  1385. function parser() {
  1386. // Reuse cached element if possible
  1387. if (!parser.nodes) {
  1388. const svg = makeInstance().size(2, 0);
  1389. svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';');
  1390. svg.attr('focusable', 'false');
  1391. svg.attr('aria-hidden', 'true');
  1392. const path = svg.path().node;
  1393. parser.nodes = {
  1394. svg,
  1395. path
  1396. };
  1397. }
  1398. if (!parser.nodes.svg.node.parentNode) {
  1399. const b = globals.document.body || globals.document.documentElement;
  1400. parser.nodes.svg.addTo(b);
  1401. }
  1402. return parser.nodes;
  1403. }
  1404. function isNulledBox(box) {
  1405. return !box.width && !box.height && !box.x && !box.y;
  1406. }
  1407. function domContains(node) {
  1408. return node === globals.document || (globals.document.documentElement.contains || function (node) {
  1409. // This is IE - it does not support contains() for top-level SVGs
  1410. while (node.parentNode) {
  1411. node = node.parentNode;
  1412. }
  1413. return node === globals.document;
  1414. }).call(globals.document.documentElement, node);
  1415. }
  1416. class Box {
  1417. constructor(...args) {
  1418. this.init(...args);
  1419. }
  1420. addOffset() {
  1421. // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
  1422. this.x += globals.window.pageXOffset;
  1423. this.y += globals.window.pageYOffset;
  1424. return new Box(this);
  1425. }
  1426. init(source) {
  1427. const base = [0, 0, 0, 0];
  1428. 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;
  1429. this.x = source[0] || 0;
  1430. this.y = source[1] || 0;
  1431. this.width = this.w = source[2] || 0;
  1432. this.height = this.h = source[3] || 0; // Add more bounding box properties
  1433. this.x2 = this.x + this.w;
  1434. this.y2 = this.y + this.h;
  1435. this.cx = this.x + this.w / 2;
  1436. this.cy = this.y + this.h / 2;
  1437. return this;
  1438. }
  1439. isNulled() {
  1440. return isNulledBox(this);
  1441. } // Merge rect box with another, return a new instance
  1442. merge(box) {
  1443. const x = Math.min(this.x, box.x);
  1444. const y = Math.min(this.y, box.y);
  1445. const width = Math.max(this.x + this.width, box.x + box.width) - x;
  1446. const height = Math.max(this.y + this.height, box.y + box.height) - y;
  1447. return new Box(x, y, width, height);
  1448. }
  1449. toArray() {
  1450. return [this.x, this.y, this.width, this.height];
  1451. }
  1452. toString() {
  1453. return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
  1454. }
  1455. transform(m) {
  1456. if (!(m instanceof Matrix)) {
  1457. m = new Matrix(m);
  1458. }
  1459. let xMin = Infinity;
  1460. let xMax = -Infinity;
  1461. let yMin = Infinity;
  1462. let yMax = -Infinity;
  1463. 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)];
  1464. pts.forEach(function (p) {
  1465. p = p.transform(m);
  1466. xMin = Math.min(xMin, p.x);
  1467. xMax = Math.max(xMax, p.x);
  1468. yMin = Math.min(yMin, p.y);
  1469. yMax = Math.max(yMax, p.y);
  1470. });
  1471. return new Box(xMin, yMin, xMax - xMin, yMax - yMin);
  1472. }
  1473. }
  1474. function getBox(el, getBBoxFn, retry) {
  1475. let box;
  1476. try {
  1477. // Try to get the box with the provided function
  1478. box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry
  1479. // by throwing an error here...
  1480. if (isNulledBox(box) && !domContains(el.node)) {
  1481. throw new Error('Element not in the dom');
  1482. }
  1483. } catch (e) {
  1484. // ... and calling the retry handler here
  1485. box = retry(el);
  1486. }
  1487. return box;
  1488. }
  1489. function bbox() {
  1490. // Function to get bbox is getBBox()
  1491. const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element
  1492. // so we can get the bbox from it when we try again
  1493. const retry = el => {
  1494. try {
  1495. const clone = el.clone().addTo(parser().svg).show();
  1496. const box = clone.node.getBBox();
  1497. clone.remove();
  1498. return box;
  1499. } catch (e) {
  1500. // We give up...
  1501. throw new Error(`Getting bbox of element "${el.node.nodeName}" is not possible: ${e.toString()}`);
  1502. }
  1503. };
  1504. const box = getBox(this, getBBox, retry);
  1505. const bbox = new Box(box);
  1506. return bbox;
  1507. }
  1508. function rbox(el) {
  1509. const getRBox = node => node.getBoundingClientRect();
  1510. const retry = el => {
  1511. // There is no point in trying tricks here because if we insert the element into the dom ourselves
  1512. // it obviously will be at the wrong position
  1513. throw new Error(`Getting rbox of element "${el.node.nodeName}" is not possible`);
  1514. };
  1515. const box = getBox(this, getRBox, retry);
  1516. const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element
  1517. if (el) {
  1518. return rbox.transform(el.screenCTM().inverseO());
  1519. } // Else we want it in absolute screen coordinates
  1520. // Therefore we need to add the scrollOffset
  1521. return rbox.addOffset();
  1522. } // Checks whether the given point is inside the bounding box
  1523. function inside(x, y) {
  1524. const box = this.bbox();
  1525. return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height;
  1526. }
  1527. registerMethods({
  1528. viewbox: {
  1529. viewbox(x, y, width, height) {
  1530. // act as getter
  1531. if (x == null) return new Box(this.attr('viewBox')); // act as setter
  1532. return this.attr('viewBox', new Box(x, y, width, height));
  1533. },
  1534. zoom(level, point) {
  1535. // Its best to rely on the attributes here and here is why:
  1536. // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)
  1537. // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely
  1538. // that means, their clientRect is always as big as the content.
  1539. // Furthermore this size is incorrect if the element is further transformed by its parents
  1540. // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!
  1541. // getBBox: returns the bounding box of its content - that doesn't help!
  1542. let {
  1543. width,
  1544. height
  1545. } = this.attr(['width', 'height']); // Width and height is a string when a number with a unit is present which we can't use
  1546. // So we try clientXYZ
  1547. if (!width && !height || typeof width === 'string' || typeof height === 'string') {
  1548. width = this.node.clientWidth;
  1549. height = this.node.clientHeight;
  1550. } // Giving up...
  1551. if (!width || !height) {
  1552. throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element');
  1553. }
  1554. const v = this.viewbox();
  1555. const zoomX = width / v.width;
  1556. const zoomY = height / v.height;
  1557. const zoom = Math.min(zoomX, zoomY);
  1558. if (level == null) {
  1559. return zoom;
  1560. }
  1561. let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from
  1562. // The * 100 is a bit of wiggle room for the matrix transformation
  1563. if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100;
  1564. point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y);
  1565. const box = new Box(v).transform(new Matrix({
  1566. scale: zoomAmount,
  1567. origin: point
  1568. }));
  1569. return this.viewbox(box);
  1570. }
  1571. }
  1572. });
  1573. register(Box, 'Box');
  1574. class List extends Array {
  1575. constructor(arr = [], ...args) {
  1576. super(arr, ...args);
  1577. if (typeof arr === 'number') return this;
  1578. this.length = 0;
  1579. this.push(...arr);
  1580. }
  1581. }
  1582. extend([List], {
  1583. each(fnOrMethodName, ...args) {
  1584. if (typeof fnOrMethodName === 'function') {
  1585. return this.map((el, i, arr) => {
  1586. return fnOrMethodName.call(el, el, i, arr);
  1587. });
  1588. } else {
  1589. return this.map(el => {
  1590. return el[fnOrMethodName](...args);
  1591. });
  1592. }
  1593. },
  1594. toArray() {
  1595. return Array.prototype.concat.apply([], this);
  1596. }
  1597. });
  1598. const reserved = ['toArray', 'constructor', 'each'];
  1599. List.extend = function (methods) {
  1600. methods = methods.reduce((obj, name) => {
  1601. // Don't overwrite own methods
  1602. if (reserved.includes(name)) return obj; // Don't add private methods
  1603. if (name[0] === '_') return obj; // Relay every call to each()
  1604. obj[name] = function (...attrs) {
  1605. return this.each(name, ...attrs);
  1606. };
  1607. return obj;
  1608. }, {});
  1609. extend([List], methods);
  1610. };
  1611. function baseFind(query, parent) {
  1612. return new List(map((parent || globals.document).querySelectorAll(query), function (node) {
  1613. return adopt(node);
  1614. }));
  1615. } // Scoped find method
  1616. function find(query) {
  1617. return baseFind(query, this.node);
  1618. }
  1619. function findOne(query) {
  1620. return adopt(this.node.querySelector(query));
  1621. }
  1622. let listenerId = 0;
  1623. const windowEvents = {};
  1624. function getEvents(instance) {
  1625. let n = instance.getEventHolder(); // We dont want to save events in global space
  1626. if (n === globals.window) n = windowEvents;
  1627. if (!n.events) n.events = {};
  1628. return n.events;
  1629. }
  1630. function getEventTarget(instance) {
  1631. return instance.getEventTarget();
  1632. }
  1633. function clearEvents(instance) {
  1634. let n = instance.getEventHolder();
  1635. if (n === globals.window) n = windowEvents;
  1636. if (n.events) n.events = {};
  1637. } // Add event binder in the SVG namespace
  1638. function on(node, events, listener, binding, options) {
  1639. const l = listener.bind(binding || node);
  1640. const instance = makeInstance(node);
  1641. const bag = getEvents(instance);
  1642. const n = getEventTarget(instance); // events can be an array of events or a string of events
  1643. events = Array.isArray(events) ? events : events.split(delimiter); // add id to listener
  1644. if (!listener._svgjsListenerId) {
  1645. listener._svgjsListenerId = ++listenerId;
  1646. }
  1647. events.forEach(function (event) {
  1648. const ev = event.split('.')[0];
  1649. const ns = event.split('.')[1] || '*'; // ensure valid object
  1650. bag[ev] = bag[ev] || {};
  1651. bag[ev][ns] = bag[ev][ns] || {}; // reference listener
  1652. bag[ev][ns][listener._svgjsListenerId] = l; // add listener
  1653. n.addEventListener(ev, l, options || false);
  1654. });
  1655. } // Add event unbinder in the SVG namespace
  1656. function off(node, events, listener, options) {
  1657. const instance = makeInstance(node);
  1658. const bag = getEvents(instance);
  1659. const n = getEventTarget(instance); // listener can be a function or a number
  1660. if (typeof listener === 'function') {
  1661. listener = listener._svgjsListenerId;
  1662. if (!listener) return;
  1663. } // events can be an array of events or a string or undefined
  1664. events = Array.isArray(events) ? events : (events || '').split(delimiter);
  1665. events.forEach(function (event) {
  1666. const ev = event && event.split('.')[0];
  1667. const ns = event && event.split('.')[1];
  1668. let namespace, l;
  1669. if (listener) {
  1670. // remove listener reference
  1671. if (bag[ev] && bag[ev][ns || '*']) {
  1672. // removeListener
  1673. n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false);
  1674. delete bag[ev][ns || '*'][listener];
  1675. }
  1676. } else if (ev && ns) {
  1677. // remove all listeners for a namespaced event
  1678. if (bag[ev] && bag[ev][ns]) {
  1679. for (l in bag[ev][ns]) {
  1680. off(n, [ev, ns].join('.'), l);
  1681. }
  1682. delete bag[ev][ns];
  1683. }
  1684. } else if (ns) {
  1685. // remove all listeners for a specific namespace
  1686. for (event in bag) {
  1687. for (namespace in bag[event]) {
  1688. if (ns === namespace) {
  1689. off(n, [event, ns].join('.'));
  1690. }
  1691. }
  1692. }
  1693. } else if (ev) {
  1694. // remove all listeners for the event
  1695. if (bag[ev]) {
  1696. for (namespace in bag[ev]) {
  1697. off(n, [ev, namespace].join('.'));
  1698. }
  1699. delete bag[ev];
  1700. }
  1701. } else {
  1702. // remove all listeners on a given node
  1703. for (event in bag) {
  1704. off(n, event);
  1705. }
  1706. clearEvents(instance);
  1707. }
  1708. });
  1709. }
  1710. function dispatch(node, event, data, options) {
  1711. const n = getEventTarget(node); // Dispatch event
  1712. if (event instanceof globals.window.Event) {
  1713. n.dispatchEvent(event);
  1714. } else {
  1715. event = new globals.window.CustomEvent(event, {
  1716. detail: data,
  1717. cancelable: true,
  1718. ...options
  1719. });
  1720. n.dispatchEvent(event);
  1721. }
  1722. return event;
  1723. }
  1724. class EventTarget extends Base {
  1725. addEventListener() {}
  1726. dispatch(event, data, options) {
  1727. return dispatch(this, event, data, options);
  1728. }
  1729. dispatchEvent(event) {
  1730. const bag = this.getEventHolder().events;
  1731. if (!bag) return true;
  1732. const events = bag[event.type];
  1733. for (const i in events) {
  1734. for (const j in events[i]) {
  1735. events[i][j](event);
  1736. }
  1737. }
  1738. return !event.defaultPrevented;
  1739. } // Fire given event
  1740. fire(event, data, options) {
  1741. this.dispatch(event, data, options);
  1742. return this;
  1743. }
  1744. getEventHolder() {
  1745. return this;
  1746. }
  1747. getEventTarget() {
  1748. return this;
  1749. } // Unbind event from listener
  1750. off(event, listener, options) {
  1751. off(this, event, listener, options);
  1752. return this;
  1753. } // Bind given event to listener
  1754. on(event, listener, binding, options) {
  1755. on(this, event, listener, binding, options);
  1756. return this;
  1757. }
  1758. removeEventListener() {}
  1759. }
  1760. register(EventTarget, 'EventTarget');
  1761. function noop() {} // Default animation values
  1762. const timeline = {
  1763. duration: 400,
  1764. ease: '>',
  1765. delay: 0
  1766. }; // Default attribute values
  1767. const attrs = {
  1768. // fill and stroke
  1769. 'fill-opacity': 1,
  1770. 'stroke-opacity': 1,
  1771. 'stroke-width': 0,
  1772. 'stroke-linejoin': 'miter',
  1773. 'stroke-linecap': 'butt',
  1774. fill: '#000000',
  1775. stroke: '#000000',
  1776. opacity: 1,
  1777. // position
  1778. x: 0,
  1779. y: 0,
  1780. cx: 0,
  1781. cy: 0,
  1782. // size
  1783. width: 0,
  1784. height: 0,
  1785. // radius
  1786. r: 0,
  1787. rx: 0,
  1788. ry: 0,
  1789. // gradient
  1790. offset: 0,
  1791. 'stop-opacity': 1,
  1792. 'stop-color': '#000000',
  1793. // text
  1794. 'text-anchor': 'start'
  1795. };
  1796. var defaults = {
  1797. __proto__: null,
  1798. noop: noop,
  1799. timeline: timeline,
  1800. attrs: attrs
  1801. };
  1802. class SVGArray extends Array {
  1803. constructor(...args) {
  1804. super(...args);
  1805. this.init(...args);
  1806. }
  1807. clone() {
  1808. return new this.constructor(this);
  1809. }
  1810. init(arr) {
  1811. // This catches the case, that native map tries to create an array with new Array(1)
  1812. if (typeof arr === 'number') return this;
  1813. this.length = 0;
  1814. this.push(...this.parse(arr));
  1815. return this;
  1816. } // Parse whitespace separated string
  1817. parse(array = []) {
  1818. // If already is an array, no need to parse it
  1819. if (array instanceof Array) return array;
  1820. return array.trim().split(delimiter).map(parseFloat);
  1821. }
  1822. toArray() {
  1823. return Array.prototype.concat.apply([], this);
  1824. }
  1825. toSet() {
  1826. return new Set(this);
  1827. }
  1828. toString() {
  1829. return this.join(' ');
  1830. } // Flattens the array if needed
  1831. valueOf() {
  1832. const ret = [];
  1833. ret.push(...this);
  1834. return ret;
  1835. }
  1836. }
  1837. class SVGNumber {
  1838. // Initialize
  1839. constructor(...args) {
  1840. this.init(...args);
  1841. }
  1842. convert(unit) {
  1843. return new SVGNumber(this.value, unit);
  1844. } // Divide number
  1845. divide(number) {
  1846. number = new SVGNumber(number);
  1847. return new SVGNumber(this / number, this.unit || number.unit);
  1848. }
  1849. init(value, unit) {
  1850. unit = Array.isArray(value) ? value[1] : unit;
  1851. value = Array.isArray(value) ? value[0] : value; // initialize defaults
  1852. this.value = 0;
  1853. this.unit = unit || ''; // parse value
  1854. if (typeof value === 'number') {
  1855. // ensure a valid numeric value
  1856. this.value = isNaN(value) ? 0 : !isFinite(value) ? value < 0 ? -3.4e+38 : +3.4e+38 : value;
  1857. } else if (typeof value === 'string') {
  1858. unit = value.match(numberAndUnit);
  1859. if (unit) {
  1860. // make value numeric
  1861. this.value = parseFloat(unit[1]); // normalize
  1862. if (unit[5] === '%') {
  1863. this.value /= 100;
  1864. } else if (unit[5] === 's') {
  1865. this.value *= 1000;
  1866. } // store unit
  1867. this.unit = unit[5];
  1868. }
  1869. } else {
  1870. if (value instanceof SVGNumber) {
  1871. this.value = value.valueOf();
  1872. this.unit = value.unit;
  1873. }
  1874. }
  1875. return this;
  1876. } // Subtract number
  1877. minus(number) {
  1878. number = new SVGNumber(number);
  1879. return new SVGNumber(this - number, this.unit || number.unit);
  1880. } // Add number
  1881. plus(number) {
  1882. number = new SVGNumber(number);
  1883. return new SVGNumber(this + number, this.unit || number.unit);
  1884. } // Multiply number
  1885. times(number) {
  1886. number = new SVGNumber(number);
  1887. return new SVGNumber(this * number, this.unit || number.unit);
  1888. }
  1889. toArray() {
  1890. return [this.value, this.unit];
  1891. }
  1892. toJSON() {
  1893. return this.toString();
  1894. }
  1895. toString() {
  1896. return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit;
  1897. }
  1898. valueOf() {
  1899. return this.value;
  1900. }
  1901. }
  1902. const hooks = [];
  1903. function registerAttrHook(fn) {
  1904. hooks.push(fn);
  1905. } // Set svg element attribute
  1906. function attr(attr, val, ns) {
  1907. // act as full getter
  1908. if (attr == null) {
  1909. // get an object of attributes
  1910. attr = {};
  1911. val = this.node.attributes;
  1912. for (const node of val) {
  1913. attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue;
  1914. }
  1915. return attr;
  1916. } else if (attr instanceof Array) {
  1917. // loop through array and get all values
  1918. return attr.reduce((last, curr) => {
  1919. last[curr] = this.attr(curr);
  1920. return last;
  1921. }, {});
  1922. } else if (typeof attr === 'object' && attr.constructor === Object) {
  1923. // apply every attribute individually if an object is passed
  1924. for (val in attr) this.attr(val, attr[val]);
  1925. } else if (val === null) {
  1926. // remove value
  1927. this.node.removeAttribute(attr);
  1928. } else if (val == null) {
  1929. // act as a getter if the first and only argument is not an object
  1930. val = this.node.getAttribute(attr);
  1931. return val == null ? attrs[attr] : isNumber.test(val) ? parseFloat(val) : val;
  1932. } else {
  1933. // Loop through hooks and execute them to convert value
  1934. val = hooks.reduce((_val, hook) => {
  1935. return hook(attr, _val, this);
  1936. }, val); // ensure correct numeric values (also accepts NaN and Infinity)
  1937. if (typeof val === 'number') {
  1938. val = new SVGNumber(val);
  1939. } else if (Color.isColor(val)) {
  1940. // ensure full hex color
  1941. val = new Color(val);
  1942. } else if (val.constructor === Array) {
  1943. // Check for plain arrays and parse array values
  1944. val = new SVGArray(val);
  1945. } // if the passed attribute is leading...
  1946. if (attr === 'leading') {
  1947. // ... call the leading method instead
  1948. if (this.leading) {
  1949. this.leading(val);
  1950. }
  1951. } else {
  1952. // set given attribute on node
  1953. typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString()) : this.node.setAttribute(attr, val.toString());
  1954. } // rebuild if required
  1955. if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
  1956. this.rebuild();
  1957. }
  1958. }
  1959. return this;
  1960. }
  1961. class Dom extends EventTarget {
  1962. constructor(node, attrs) {
  1963. super();
  1964. this.node = node;
  1965. this.type = node.nodeName;
  1966. if (attrs && node !== attrs) {
  1967. this.attr(attrs);
  1968. }
  1969. } // Add given element at a position
  1970. add(element, i) {
  1971. element = makeInstance(element); // If non-root svg nodes are added we have to remove their namespaces
  1972. if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {
  1973. element.removeNamespace();
  1974. }
  1975. if (i == null) {
  1976. this.node.appendChild(element.node);
  1977. } else if (element.node !== this.node.childNodes[i]) {
  1978. this.node.insertBefore(element.node, this.node.childNodes[i]);
  1979. }
  1980. return this;
  1981. } // Add element to given container and return self
  1982. addTo(parent, i) {
  1983. return makeInstance(parent).put(this, i);
  1984. } // Returns all child elements
  1985. children() {
  1986. return new List(map(this.node.children, function (node) {
  1987. return adopt(node);
  1988. }));
  1989. } // Remove all elements in this container
  1990. clear() {
  1991. // remove children
  1992. while (this.node.hasChildNodes()) {
  1993. this.node.removeChild(this.node.lastChild);
  1994. }
  1995. return this;
  1996. } // Clone element
  1997. clone(deep = true, assignNewIds = true) {
  1998. // write dom data to the dom so the clone can pickup the data
  1999. this.writeDataToDom(); // clone element
  2000. let nodeClone = this.node.cloneNode(deep);
  2001. if (assignNewIds) {
  2002. // assign new id
  2003. nodeClone = assignNewId(nodeClone);
  2004. }
  2005. return new this.constructor(nodeClone);
  2006. } // Iterates over all children and invokes a given block
  2007. each(block, deep) {
  2008. const children = this.children();
  2009. let i, il;
  2010. for (i = 0, il = children.length; i < il; i++) {
  2011. block.apply(children[i], [i, children]);
  2012. if (deep) {
  2013. children[i].each(block, deep);
  2014. }
  2015. }
  2016. return this;
  2017. }
  2018. element(nodeName, attrs) {
  2019. return this.put(new Dom(create(nodeName), attrs));
  2020. } // Get first child
  2021. first() {
  2022. return adopt(this.node.firstChild);
  2023. } // Get a element at the given index
  2024. get(i) {
  2025. return adopt(this.node.childNodes[i]);
  2026. }
  2027. getEventHolder() {
  2028. return this.node;
  2029. }
  2030. getEventTarget() {
  2031. return this.node;
  2032. } // Checks if the given element is a child
  2033. has(element) {
  2034. return this.index(element) >= 0;
  2035. }
  2036. html(htmlOrFn, outerHTML) {
  2037. return this.xml(htmlOrFn, outerHTML, html);
  2038. } // Get / set id
  2039. id(id) {
  2040. // generate new id if no id set
  2041. if (typeof id === 'undefined' && !this.node.id) {
  2042. this.node.id = eid(this.type);
  2043. } // don't set directly with this.node.id to make `null` work correctly
  2044. return this.attr('id', id);
  2045. } // Gets index of given element
  2046. index(element) {
  2047. return [].slice.call(this.node.childNodes).indexOf(element.node);
  2048. } // Get the last child
  2049. last() {
  2050. return adopt(this.node.lastChild);
  2051. } // matches the element vs a css selector
  2052. matches(selector) {
  2053. const el = this.node;
  2054. const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null;
  2055. return matcher && matcher.call(el, selector);
  2056. } // Returns the parent element instance
  2057. parent(type) {
  2058. let parent = this; // check for parent
  2059. if (!parent.node.parentNode) return null; // get parent element
  2060. parent = adopt(parent.node.parentNode);
  2061. if (!type) return parent; // loop through ancestors if type is given
  2062. do {
  2063. if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent;
  2064. } while (parent = adopt(parent.node.parentNode));
  2065. return parent;
  2066. } // Basically does the same as `add()` but returns the added element instead
  2067. put(element, i) {
  2068. element = makeInstance(element);
  2069. this.add(element, i);
  2070. return element;
  2071. } // Add element to given container and return container
  2072. putIn(parent, i) {
  2073. return makeInstance(parent).add(this, i);
  2074. } // Remove element
  2075. remove() {
  2076. if (this.parent()) {
  2077. this.parent().removeElement(this);
  2078. }
  2079. return this;
  2080. } // Remove a given child
  2081. removeElement(element) {
  2082. this.node.removeChild(element.node);
  2083. return this;
  2084. } // Replace this with element
  2085. replace(element) {
  2086. element = makeInstance(element);
  2087. if (this.node.parentNode) {
  2088. this.node.parentNode.replaceChild(element.node, this.node);
  2089. }
  2090. return element;
  2091. }
  2092. round(precision = 2, map = null) {
  2093. const factor = 10 ** precision;
  2094. const attrs = this.attr(map);
  2095. for (const i in attrs) {
  2096. if (typeof attrs[i] === 'number') {
  2097. attrs[i] = Math.round(attrs[i] * factor) / factor;
  2098. }
  2099. }
  2100. this.attr(attrs);
  2101. return this;
  2102. } // Import / Export raw svg
  2103. svg(svgOrFn, outerSVG) {
  2104. return this.xml(svgOrFn, outerSVG, svg);
  2105. } // Return id on string conversion
  2106. toString() {
  2107. return this.id();
  2108. }
  2109. words(text) {
  2110. // This is faster than removing all children and adding a new one
  2111. this.node.textContent = text;
  2112. return this;
  2113. }
  2114. wrap(node) {
  2115. const parent = this.parent();
  2116. if (!parent) {
  2117. return this.addTo(node);
  2118. }
  2119. const position = parent.index(this);
  2120. return parent.put(node, position).put(this);
  2121. } // write svgjs data to the dom
  2122. writeDataToDom() {
  2123. // dump variables recursively
  2124. this.each(function () {
  2125. this.writeDataToDom();
  2126. });
  2127. return this;
  2128. } // Import / Export raw svg
  2129. xml(xmlOrFn, outerXML, ns) {
  2130. if (typeof xmlOrFn === 'boolean') {
  2131. ns = outerXML;
  2132. outerXML = xmlOrFn;
  2133. xmlOrFn = null;
  2134. } // act as getter if no svg string is given
  2135. if (xmlOrFn == null || typeof xmlOrFn === 'function') {
  2136. // The default for exports is, that the outerNode is included
  2137. outerXML = outerXML == null ? true : outerXML; // write svgjs data to the dom
  2138. this.writeDataToDom();
  2139. let current = this; // An export modifier was passed
  2140. if (xmlOrFn != null) {
  2141. current = adopt(current.node.cloneNode(true)); // If the user wants outerHTML we need to process this node, too
  2142. if (outerXML) {
  2143. const result = xmlOrFn(current);
  2144. current = result || current; // The user does not want this node? Well, then he gets nothing
  2145. if (result === false) return '';
  2146. } // Deep loop through all children and apply modifier
  2147. current.each(function () {
  2148. const result = xmlOrFn(this);
  2149. const _this = result || this; // If modifier returns false, discard node
  2150. if (result === false) {
  2151. this.remove(); // If modifier returns new node, use it
  2152. } else if (result && this !== _this) {
  2153. this.replace(_this);
  2154. }
  2155. }, true);
  2156. } // Return outer or inner content
  2157. return outerXML ? current.node.outerHTML : current.node.innerHTML;
  2158. } // Act as setter if we got a string
  2159. // The default for import is, that the current node is not replaced
  2160. outerXML = outerXML == null ? false : outerXML; // Create temporary holder
  2161. const well = create('wrapper', ns);
  2162. const fragment = globals.document.createDocumentFragment(); // Dump raw svg
  2163. well.innerHTML = xmlOrFn; // Transplant nodes into the fragment
  2164. for (let len = well.children.length; len--;) {
  2165. fragment.appendChild(well.firstElementChild);
  2166. }
  2167. const parent = this.parent(); // Add the whole fragment at once
  2168. return outerXML ? this.replace(fragment) && parent : this.add(fragment);
  2169. }
  2170. }
  2171. extend(Dom, {
  2172. attr,
  2173. find,
  2174. findOne
  2175. });
  2176. register(Dom, 'Dom');
  2177. class Element extends Dom {
  2178. constructor(node, attrs) {
  2179. super(node, attrs); // initialize data object
  2180. this.dom = {}; // create circular reference
  2181. this.node.instance = this;
  2182. if (node.hasAttribute('svgjs:data')) {
  2183. // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
  2184. this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {});
  2185. }
  2186. } // Move element by its center
  2187. center(x, y) {
  2188. return this.cx(x).cy(y);
  2189. } // Move by center over x-axis
  2190. cx(x) {
  2191. return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2);
  2192. } // Move by center over y-axis
  2193. cy(y) {
  2194. return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2);
  2195. } // Get defs
  2196. defs() {
  2197. const root = this.root();
  2198. return root && root.defs();
  2199. } // Relative move over x and y axes
  2200. dmove(x, y) {
  2201. return this.dx(x).dy(y);
  2202. } // Relative move over x axis
  2203. dx(x = 0) {
  2204. return this.x(new SVGNumber(x).plus(this.x()));
  2205. } // Relative move over y axis
  2206. dy(y = 0) {
  2207. return this.y(new SVGNumber(y).plus(this.y()));
  2208. }
  2209. getEventHolder() {
  2210. return this;
  2211. } // Set height of element
  2212. height(height) {
  2213. return this.attr('height', height);
  2214. } // Move element to given x and y values
  2215. move(x, y) {
  2216. return this.x(x).y(y);
  2217. } // return array of all ancestors of given type up to the root svg
  2218. parents(until = this.root()) {
  2219. const isSelector = typeof until === 'string';
  2220. if (!isSelector) {
  2221. until = makeInstance(until);
  2222. }
  2223. const parents = new List();
  2224. let parent = this;
  2225. while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') {
  2226. parents.push(parent);
  2227. if (!isSelector && parent.node === until.node) {
  2228. break;
  2229. }
  2230. if (isSelector && parent.matches(until)) {
  2231. break;
  2232. }
  2233. if (parent.node === this.root().node) {
  2234. // We worked our way to the root and didn't match `until`
  2235. return null;
  2236. }
  2237. }
  2238. return parents;
  2239. } // Get referenced element form attribute value
  2240. reference(attr) {
  2241. attr = this.attr(attr);
  2242. if (!attr) return null;
  2243. const m = (attr + '').match(reference);
  2244. return m ? makeInstance(m[1]) : null;
  2245. } // Get parent document
  2246. root() {
  2247. const p = this.parent(getClass(root));
  2248. return p && p.root();
  2249. } // set given data to the elements data property
  2250. setData(o) {
  2251. this.dom = o;
  2252. return this;
  2253. } // Set element size to given width and height
  2254. size(width, height) {
  2255. const p = proportionalSize(this, width, height);
  2256. return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height));
  2257. } // Set width of element
  2258. width(width) {
  2259. return this.attr('width', width);
  2260. } // write svgjs data to the dom
  2261. writeDataToDom() {
  2262. // remove previously set data
  2263. this.node.removeAttribute('svgjs:data');
  2264. if (Object.keys(this.dom).length) {
  2265. this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)); // see #428
  2266. }
  2267. return super.writeDataToDom();
  2268. } // Move over x-axis
  2269. x(x) {
  2270. return this.attr('x', x);
  2271. } // Move over y-axis
  2272. y(y) {
  2273. return this.attr('y', y);
  2274. }
  2275. }
  2276. extend(Element, {
  2277. bbox,
  2278. rbox,
  2279. inside,
  2280. point,
  2281. ctm,
  2282. screenCTM
  2283. });
  2284. register(Element, 'Element');
  2285. const sugar = {
  2286. stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
  2287. fill: ['color', 'opacity', 'rule'],
  2288. prefix: function (t, a) {
  2289. return a === 'color' ? t : t + '-' + a;
  2290. }
  2291. } // Add sugar for fill and stroke
  2292. ;
  2293. ['fill', 'stroke'].forEach(function (m) {
  2294. const extension = {};
  2295. let i;
  2296. extension[m] = function (o) {
  2297. if (typeof o === 'undefined') {
  2298. return this.attr(m);
  2299. }
  2300. if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) {
  2301. this.attr(m, o);
  2302. } else {
  2303. // set all attributes from sugar.fill and sugar.stroke list
  2304. for (i = sugar[m].length - 1; i >= 0; i--) {
  2305. if (o[sugar[m][i]] != null) {
  2306. this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]);
  2307. }
  2308. }
  2309. }
  2310. return this;
  2311. };
  2312. registerMethods(['Element', 'Runner'], extension);
  2313. });
  2314. registerMethods(['Element', 'Runner'], {
  2315. // Let the user set the matrix directly
  2316. matrix: function (mat, b, c, d, e, f) {
  2317. // Act as a getter
  2318. if (mat == null) {
  2319. return new Matrix(this);
  2320. } // Act as a setter, the user can pass a matrix or a set of numbers
  2321. return this.attr('transform', new Matrix(mat, b, c, d, e, f));
  2322. },
  2323. // Map rotation to transform
  2324. rotate: function (angle, cx, cy) {
  2325. return this.transform({
  2326. rotate: angle,
  2327. ox: cx,
  2328. oy: cy
  2329. }, true);
  2330. },
  2331. // Map skew to transform
  2332. skew: function (x, y, cx, cy) {
  2333. return arguments.length === 1 || arguments.length === 3 ? this.transform({
  2334. skew: x,
  2335. ox: y,
  2336. oy: cx
  2337. }, true) : this.transform({
  2338. skew: [x, y],
  2339. ox: cx,
  2340. oy: cy
  2341. }, true);
  2342. },
  2343. shear: function (lam, cx, cy) {
  2344. return this.transform({
  2345. shear: lam,
  2346. ox: cx,
  2347. oy: cy
  2348. }, true);
  2349. },
  2350. // Map scale to transform
  2351. scale: function (x, y, cx, cy) {
  2352. return arguments.length === 1 || arguments.length === 3 ? this.transform({
  2353. scale: x,
  2354. ox: y,
  2355. oy: cx
  2356. }, true) : this.transform({
  2357. scale: [x, y],
  2358. ox: cx,
  2359. oy: cy
  2360. }, true);
  2361. },
  2362. // Map translate to transform
  2363. translate: function (x, y) {
  2364. return this.transform({
  2365. translate: [x, y]
  2366. }, true);
  2367. },
  2368. // Map relative translations to transform
  2369. relative: function (x, y) {
  2370. return this.transform({
  2371. relative: [x, y]
  2372. }, true);
  2373. },
  2374. // Map flip to transform
  2375. flip: function (direction = 'both', origin = 'center') {
  2376. if ('xybothtrue'.indexOf(direction) === -1) {
  2377. origin = direction;
  2378. direction = 'both';
  2379. }
  2380. return this.transform({
  2381. flip: direction,
  2382. origin: origin
  2383. }, true);
  2384. },
  2385. // Opacity
  2386. opacity: function (value) {
  2387. return this.attr('opacity', value);
  2388. }
  2389. });
  2390. registerMethods('radius', {
  2391. // Add x and y radius
  2392. radius: function (x, y = x) {
  2393. const type = (this._element || this).type;
  2394. return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y);
  2395. }
  2396. });
  2397. registerMethods('Path', {
  2398. // Get path length
  2399. length: function () {
  2400. return this.node.getTotalLength();
  2401. },
  2402. // Get point at length
  2403. pointAt: function (length) {
  2404. return new Point(this.node.getPointAtLength(length));
  2405. }
  2406. });
  2407. registerMethods(['Element', 'Runner'], {
  2408. // Set font
  2409. font: function (a, v) {
  2410. if (typeof a === 'object') {
  2411. for (v in a) this.font(v, a[v]);
  2412. return this;
  2413. }
  2414. 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);
  2415. }
  2416. }); // Add events to elements
  2417. const methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
  2418. // add event to Element
  2419. const fn = function (f) {
  2420. if (f === null) {
  2421. this.off(event);
  2422. } else {
  2423. this.on(event, f);
  2424. }
  2425. return this;
  2426. };
  2427. last[event] = fn;
  2428. return last;
  2429. }, {});
  2430. registerMethods('Element', methods);
  2431. function untransform() {
  2432. return this.attr('transform', null);
  2433. } // merge the whole transformation chain into one matrix and returns it
  2434. function matrixify() {
  2435. const matrix = (this.attr('transform') || '' // split transformations
  2436. ).split(transforms).slice(0, -1).map(function (str) {
  2437. // generate key => value pairs
  2438. const kv = str.trim().split('(');
  2439. return [kv[0], kv[1].split(delimiter).map(function (str) {
  2440. return parseFloat(str);
  2441. })];
  2442. }).reverse() // merge every transformation into one matrix
  2443. .reduce(function (matrix, transform) {
  2444. if (transform[0] === 'matrix') {
  2445. return matrix.lmultiply(Matrix.fromArray(transform[1]));
  2446. }
  2447. return matrix[transform[0]].apply(matrix, transform[1]);
  2448. }, new Matrix());
  2449. return matrix;
  2450. } // add an element to another parent without changing the visual representation on the screen
  2451. function toParent(parent, i) {
  2452. if (this === parent) return this;
  2453. const ctm = this.screenCTM();
  2454. const pCtm = parent.screenCTM().inverse();
  2455. this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm));
  2456. return this;
  2457. } // same as above with parent equals root-svg
  2458. function toRoot(i) {
  2459. return this.toParent(this.root(), i);
  2460. } // Add transformations
  2461. function transform(o, relative) {
  2462. // Act as a getter if no object was passed
  2463. if (o == null || typeof o === 'string') {
  2464. const decomposed = new Matrix(this).decompose();
  2465. return o == null ? decomposed : decomposed[o];
  2466. }
  2467. if (!Matrix.isMatrixLike(o)) {
  2468. // Set the origin according to the defined transform
  2469. o = { ...o,
  2470. origin: getOrigin(o, this)
  2471. };
  2472. } // The user can pass a boolean, an Element or an Matrix or nothing
  2473. const cleanRelative = relative === true ? this : relative || false;
  2474. const result = new Matrix(cleanRelative).transform(o);
  2475. return this.attr('transform', result);
  2476. }
  2477. registerMethods('Element', {
  2478. untransform,
  2479. matrixify,
  2480. toParent,
  2481. toRoot,
  2482. transform
  2483. });
  2484. class Container extends Element {
  2485. flatten(parent = this, index) {
  2486. this.each(function () {
  2487. if (this instanceof Container) {
  2488. return this.flatten().ungroup();
  2489. }
  2490. });
  2491. return this;
  2492. }
  2493. ungroup(parent = this.parent(), index = parent.index(this)) {
  2494. // when parent != this, we want append all elements to the end
  2495. index = index === -1 ? parent.children().length : index;
  2496. this.each(function (i, children) {
  2497. // reverse each
  2498. return children[children.length - i - 1].toParent(parent, index);
  2499. });
  2500. return this.remove();
  2501. }
  2502. }
  2503. register(Container, 'Container');
  2504. class Defs extends Container {
  2505. constructor(node, attrs = node) {
  2506. super(nodeOrNew('defs', node), attrs);
  2507. }
  2508. flatten() {
  2509. return this;
  2510. }
  2511. ungroup() {
  2512. return this;
  2513. }
  2514. }
  2515. register(Defs, 'Defs');
  2516. class Shape extends Element {}
  2517. register(Shape, 'Shape');
  2518. function rx(rx) {
  2519. return this.attr('rx', rx);
  2520. } // Radius y value
  2521. function ry(ry) {
  2522. return this.attr('ry', ry);
  2523. } // Move over x-axis
  2524. function x$3(x) {
  2525. return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());
  2526. } // Move over y-axis
  2527. function y$3(y) {
  2528. return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());
  2529. } // Move by center over x-axis
  2530. function cx$1(x) {
  2531. return this.attr('cx', x);
  2532. } // Move by center over y-axis
  2533. function cy$1(y) {
  2534. return this.attr('cy', y);
  2535. } // Set width of element
  2536. function width$2(width) {
  2537. return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));
  2538. } // Set height of element
  2539. function height$2(height) {
  2540. return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
  2541. }
  2542. var circled = {
  2543. __proto__: null,
  2544. rx: rx,
  2545. ry: ry,
  2546. x: x$3,
  2547. y: y$3,
  2548. cx: cx$1,
  2549. cy: cy$1,
  2550. width: width$2,
  2551. height: height$2
  2552. };
  2553. class Ellipse extends Shape {
  2554. constructor(node, attrs = node) {
  2555. super(nodeOrNew('ellipse', node), attrs);
  2556. }
  2557. size(width, height) {
  2558. const p = proportionalSize(this, width, height);
  2559. return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
  2560. }
  2561. }
  2562. extend(Ellipse, circled);
  2563. registerMethods('Container', {
  2564. // Create an ellipse
  2565. ellipse: wrapWithAttrCheck(function (width = 0, height = width) {
  2566. return this.put(new Ellipse()).size(width, height).move(0, 0);
  2567. })
  2568. });
  2569. register(Ellipse, 'Ellipse');
  2570. class Fragment extends Dom {
  2571. constructor(node = globals.document.createDocumentFragment()) {
  2572. super(node);
  2573. } // Import / Export raw xml
  2574. xml(xmlOrFn, outerXML, ns) {
  2575. if (typeof xmlOrFn === 'boolean') {
  2576. ns = outerXML;
  2577. outerXML = xmlOrFn;
  2578. xmlOrFn = null;
  2579. } // because this is a fragment we have to put all elements into a wrapper first
  2580. // before we can get the innerXML from it
  2581. if (xmlOrFn == null || typeof xmlOrFn === 'function') {
  2582. const wrapper = new Dom(create('wrapper', ns));
  2583. wrapper.add(this.node.cloneNode(true));
  2584. return wrapper.xml(false, ns);
  2585. } // Act as setter if we got a string
  2586. return super.xml(xmlOrFn, false, ns);
  2587. }
  2588. }
  2589. register(Fragment, 'Fragment');
  2590. function from(x, y) {
  2591. return (this._element || this).type === 'radialGradient' ? this.attr({
  2592. fx: new SVGNumber(x),
  2593. fy: new SVGNumber(y)
  2594. }) : this.attr({
  2595. x1: new SVGNumber(x),
  2596. y1: new SVGNumber(y)
  2597. });
  2598. }
  2599. function to(x, y) {
  2600. return (this._element || this).type === 'radialGradient' ? this.attr({
  2601. cx: new SVGNumber(x),
  2602. cy: new SVGNumber(y)
  2603. }) : this.attr({
  2604. x2: new SVGNumber(x),
  2605. y2: new SVGNumber(y)
  2606. });
  2607. }
  2608. var gradiented = {
  2609. __proto__: null,
  2610. from: from,
  2611. to: to
  2612. };
  2613. class Gradient extends Container {
  2614. constructor(type, attrs) {
  2615. super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs);
  2616. } // custom attr to handle transform
  2617. attr(a, b, c) {
  2618. if (a === 'transform') a = 'gradientTransform';
  2619. return super.attr(a, b, c);
  2620. }
  2621. bbox() {
  2622. return new Box();
  2623. }
  2624. targets() {
  2625. return baseFind('svg [fill*=' + this.id() + ']');
  2626. } // Alias string conversion to fill
  2627. toString() {
  2628. return this.url();
  2629. } // Update gradient
  2630. update(block) {
  2631. // remove all stops
  2632. this.clear(); // invoke passed block
  2633. if (typeof block === 'function') {
  2634. block.call(this, this);
  2635. }
  2636. return this;
  2637. } // Return the fill id
  2638. url() {
  2639. return 'url(#' + this.id() + ')';
  2640. }
  2641. }
  2642. extend(Gradient, gradiented);
  2643. registerMethods({
  2644. Container: {
  2645. // Create gradient element in defs
  2646. gradient(...args) {
  2647. return this.defs().gradient(...args);
  2648. }
  2649. },
  2650. // define gradient
  2651. Defs: {
  2652. gradient: wrapWithAttrCheck(function (type, block) {
  2653. return this.put(new Gradient(type)).update(block);
  2654. })
  2655. }
  2656. });
  2657. register(Gradient, 'Gradient');
  2658. class Pattern extends Container {
  2659. // Initialize node
  2660. constructor(node, attrs = node) {
  2661. super(nodeOrNew('pattern', node), attrs);
  2662. } // custom attr to handle transform
  2663. attr(a, b, c) {
  2664. if (a === 'transform') a = 'patternTransform';
  2665. return super.attr(a, b, c);
  2666. }
  2667. bbox() {
  2668. return new Box();
  2669. }
  2670. targets() {
  2671. return baseFind('svg [fill*=' + this.id() + ']');
  2672. } // Alias string conversion to fill
  2673. toString() {
  2674. return this.url();
  2675. } // Update pattern by rebuilding
  2676. update(block) {
  2677. // remove content
  2678. this.clear(); // invoke passed block
  2679. if (typeof block === 'function') {
  2680. block.call(this, this);
  2681. }
  2682. return this;
  2683. } // Return the fill id
  2684. url() {
  2685. return 'url(#' + this.id() + ')';
  2686. }
  2687. }
  2688. registerMethods({
  2689. Container: {
  2690. // Create pattern element in defs
  2691. pattern(...args) {
  2692. return this.defs().pattern(...args);
  2693. }
  2694. },
  2695. Defs: {
  2696. pattern: wrapWithAttrCheck(function (width, height, block) {
  2697. return this.put(new Pattern()).update(block).attr({
  2698. x: 0,
  2699. y: 0,
  2700. width: width,
  2701. height: height,
  2702. patternUnits: 'userSpaceOnUse'
  2703. });
  2704. })
  2705. }
  2706. });
  2707. register(Pattern, 'Pattern');
  2708. class Image extends Shape {
  2709. constructor(node, attrs = node) {
  2710. super(nodeOrNew('image', node), attrs);
  2711. } // (re)load image
  2712. load(url, callback) {
  2713. if (!url) return this;
  2714. const img = new globals.window.Image();
  2715. on(img, 'load', function (e) {
  2716. const p = this.parent(Pattern); // ensure image size
  2717. if (this.width() === 0 && this.height() === 0) {
  2718. this.size(img.width, img.height);
  2719. }
  2720. if (p instanceof Pattern) {
  2721. // ensure pattern size if not set
  2722. if (p.width() === 0 && p.height() === 0) {
  2723. p.size(this.width(), this.height());
  2724. }
  2725. }
  2726. if (typeof callback === 'function') {
  2727. callback.call(this, e);
  2728. }
  2729. }, this);
  2730. on(img, 'load error', function () {
  2731. // dont forget to unbind memory leaking events
  2732. off(img);
  2733. });
  2734. return this.attr('href', img.src = url, xlink);
  2735. }
  2736. }
  2737. registerAttrHook(function (attr, val, _this) {
  2738. // convert image fill and stroke to patterns
  2739. if (attr === 'fill' || attr === 'stroke') {
  2740. if (isImage.test(val)) {
  2741. val = _this.root().defs().image(val);
  2742. }
  2743. }
  2744. if (val instanceof Image) {
  2745. val = _this.root().defs().pattern(0, 0, pattern => {
  2746. pattern.add(val);
  2747. });
  2748. }
  2749. return val;
  2750. });
  2751. registerMethods({
  2752. Container: {
  2753. // create image element, load image and set its size
  2754. image: wrapWithAttrCheck(function (source, callback) {
  2755. return this.put(new Image()).size(0, 0).load(source, callback);
  2756. })
  2757. }
  2758. });
  2759. register(Image, 'Image');
  2760. class PointArray extends SVGArray {
  2761. // Get bounding box of points
  2762. bbox() {
  2763. let maxX = -Infinity;
  2764. let maxY = -Infinity;
  2765. let minX = Infinity;
  2766. let minY = Infinity;
  2767. this.forEach(function (el) {
  2768. maxX = Math.max(el[0], maxX);
  2769. maxY = Math.max(el[1], maxY);
  2770. minX = Math.min(el[0], minX);
  2771. minY = Math.min(el[1], minY);
  2772. });
  2773. return new Box(minX, minY, maxX - minX, maxY - minY);
  2774. } // Move point string
  2775. move(x, y) {
  2776. const box = this.bbox(); // get relative offset
  2777. x -= box.x;
  2778. y -= box.y; // move every point
  2779. if (!isNaN(x) && !isNaN(y)) {
  2780. for (let i = this.length - 1; i >= 0; i--) {
  2781. this[i] = [this[i][0] + x, this[i][1] + y];
  2782. }
  2783. }
  2784. return this;
  2785. } // Parse point string and flat array
  2786. parse(array = [0, 0]) {
  2787. const points = []; // if it is an array, we flatten it and therefore clone it to 1 depths
  2788. if (array instanceof Array) {
  2789. array = Array.prototype.concat.apply([], array);
  2790. } else {
  2791. // Else, it is considered as a string
  2792. // parse points
  2793. array = array.trim().split(delimiter).map(parseFloat);
  2794. } // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
  2795. // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
  2796. if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples
  2797. for (let i = 0, len = array.length; i < len; i = i + 2) {
  2798. points.push([array[i], array[i + 1]]);
  2799. }
  2800. return points;
  2801. } // Resize poly string
  2802. size(width, height) {
  2803. let i;
  2804. const box = this.bbox(); // recalculate position of all points according to new size
  2805. for (i = this.length - 1; i >= 0; i--) {
  2806. if (box.width) this[i][0] = (this[i][0] - box.x) * width / box.width + box.x;
  2807. if (box.height) this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
  2808. }
  2809. return this;
  2810. } // Convert array to line object
  2811. toLine() {
  2812. return {
  2813. x1: this[0][0],
  2814. y1: this[0][1],
  2815. x2: this[1][0],
  2816. y2: this[1][1]
  2817. };
  2818. } // Convert array to string
  2819. toString() {
  2820. const array = []; // convert to a poly point string
  2821. for (let i = 0, il = this.length; i < il; i++) {
  2822. array.push(this[i].join(','));
  2823. }
  2824. return array.join(' ');
  2825. }
  2826. transform(m) {
  2827. return this.clone().transformO(m);
  2828. } // transform points with matrix (similar to Point.transform)
  2829. transformO(m) {
  2830. if (!Matrix.isMatrixLike(m)) {
  2831. m = new Matrix(m);
  2832. }
  2833. for (let i = this.length; i--;) {
  2834. // Perform the matrix multiplication
  2835. const [x, y] = this[i];
  2836. this[i][0] = m.a * x + m.c * y + m.e;
  2837. this[i][1] = m.b * x + m.d * y + m.f;
  2838. }
  2839. return this;
  2840. }
  2841. }
  2842. const MorphArray = PointArray; // Move by left top corner over x-axis
  2843. function x$2(x) {
  2844. return x == null ? this.bbox().x : this.move(x, this.bbox().y);
  2845. } // Move by left top corner over y-axis
  2846. function y$2(y) {
  2847. return y == null ? this.bbox().y : this.move(this.bbox().x, y);
  2848. } // Set width of element
  2849. function width$1(width) {
  2850. const b = this.bbox();
  2851. return width == null ? b.width : this.size(width, b.height);
  2852. } // Set height of element
  2853. function height$1(height) {
  2854. const b = this.bbox();
  2855. return height == null ? b.height : this.size(b.width, height);
  2856. }
  2857. var pointed = {
  2858. __proto__: null,
  2859. MorphArray: MorphArray,
  2860. x: x$2,
  2861. y: y$2,
  2862. width: width$1,
  2863. height: height$1
  2864. };
  2865. class Line extends Shape {
  2866. // Initialize node
  2867. constructor(node, attrs = node) {
  2868. super(nodeOrNew('line', node), attrs);
  2869. } // Get array
  2870. array() {
  2871. return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);
  2872. } // Move by left top corner
  2873. move(x, y) {
  2874. return this.attr(this.array().move(x, y).toLine());
  2875. } // Overwrite native plot() method
  2876. plot(x1, y1, x2, y2) {
  2877. if (x1 == null) {
  2878. return this.array();
  2879. } else if (typeof y1 !== 'undefined') {
  2880. x1 = {
  2881. x1,
  2882. y1,
  2883. x2,
  2884. y2
  2885. };
  2886. } else {
  2887. x1 = new PointArray(x1).toLine();
  2888. }
  2889. return this.attr(x1);
  2890. } // Set element size to given width and height
  2891. size(width, height) {
  2892. const p = proportionalSize(this, width, height);
  2893. return this.attr(this.array().size(p.width, p.height).toLine());
  2894. }
  2895. }
  2896. extend(Line, pointed);
  2897. registerMethods({
  2898. Container: {
  2899. // Create a line element
  2900. line: wrapWithAttrCheck(function (...args) {
  2901. // make sure plot is called as a setter
  2902. // x1 is not necessarily a number, it can also be an array, a string and a PointArray
  2903. return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]);
  2904. })
  2905. }
  2906. });
  2907. register(Line, 'Line');
  2908. class Marker extends Container {
  2909. // Initialize node
  2910. constructor(node, attrs = node) {
  2911. super(nodeOrNew('marker', node), attrs);
  2912. } // Set height of element
  2913. height(height) {
  2914. return this.attr('markerHeight', height);
  2915. }
  2916. orient(orient) {
  2917. return this.attr('orient', orient);
  2918. } // Set marker refX and refY
  2919. ref(x, y) {
  2920. return this.attr('refX', x).attr('refY', y);
  2921. } // Return the fill id
  2922. toString() {
  2923. return 'url(#' + this.id() + ')';
  2924. } // Update marker
  2925. update(block) {
  2926. // remove all content
  2927. this.clear(); // invoke passed block
  2928. if (typeof block === 'function') {
  2929. block.call(this, this);
  2930. }
  2931. return this;
  2932. } // Set width of element
  2933. width(width) {
  2934. return this.attr('markerWidth', width);
  2935. }
  2936. }
  2937. registerMethods({
  2938. Container: {
  2939. marker(...args) {
  2940. // Create marker element in defs
  2941. return this.defs().marker(...args);
  2942. }
  2943. },
  2944. Defs: {
  2945. // Create marker
  2946. marker: wrapWithAttrCheck(function (width, height, block) {
  2947. // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
  2948. return this.put(new Marker()).size(width, height).ref(width / 2, height / 2).viewbox(0, 0, width, height).attr('orient', 'auto').update(block);
  2949. })
  2950. },
  2951. marker: {
  2952. // Create and attach markers
  2953. marker(marker, width, height, block) {
  2954. let attr = ['marker']; // Build attribute name
  2955. if (marker !== 'all') attr.push(marker);
  2956. attr = attr.join('-'); // Set marker attribute
  2957. marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);
  2958. return this.attr(attr, marker);
  2959. }
  2960. }
  2961. });
  2962. register(Marker, 'Marker');
  2963. /***
  2964. Base Class
  2965. ==========
  2966. The base stepper class that will be
  2967. ***/
  2968. function makeSetterGetter(k, f) {
  2969. return function (v) {
  2970. if (v == null) return this[k];
  2971. this[k] = v;
  2972. if (f) f.call(this);
  2973. return this;
  2974. };
  2975. }
  2976. const easing = {
  2977. '-': function (pos) {
  2978. return pos;
  2979. },
  2980. '<>': function (pos) {
  2981. return -Math.cos(pos * Math.PI) / 2 + 0.5;
  2982. },
  2983. '>': function (pos) {
  2984. return Math.sin(pos * Math.PI / 2);
  2985. },
  2986. '<': function (pos) {
  2987. return -Math.cos(pos * Math.PI / 2) + 1;
  2988. },
  2989. bezier: function (x1, y1, x2, y2) {
  2990. // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
  2991. return function (t) {
  2992. if (t < 0) {
  2993. if (x1 > 0) {
  2994. return y1 / x1 * t;
  2995. } else if (x2 > 0) {
  2996. return y2 / x2 * t;
  2997. } else {
  2998. return 0;
  2999. }
  3000. } else if (t > 1) {
  3001. if (x2 < 1) {
  3002. return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2);
  3003. } else if (x1 < 1) {
  3004. return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1);
  3005. } else {
  3006. return 1;
  3007. }
  3008. } else {
  3009. return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3;
  3010. }
  3011. };
  3012. },
  3013. // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
  3014. steps: function (steps, stepPosition = 'end') {
  3015. // deal with "jump-" prefix
  3016. stepPosition = stepPosition.split('-').reverse()[0];
  3017. let jumps = steps;
  3018. if (stepPosition === 'none') {
  3019. --jumps;
  3020. } else if (stepPosition === 'both') {
  3021. ++jumps;
  3022. } // The beforeFlag is essentially useless
  3023. return (t, beforeFlag = false) => {
  3024. // Step is called currentStep in referenced url
  3025. let step = Math.floor(t * steps);
  3026. const jumping = t * step % 1 === 0;
  3027. if (stepPosition === 'start' || stepPosition === 'both') {
  3028. ++step;
  3029. }
  3030. if (beforeFlag && jumping) {
  3031. --step;
  3032. }
  3033. if (t >= 0 && step < 0) {
  3034. step = 0;
  3035. }
  3036. if (t <= 1 && step > jumps) {
  3037. step = jumps;
  3038. }
  3039. return step / jumps;
  3040. };
  3041. }
  3042. };
  3043. class Stepper {
  3044. done() {
  3045. return false;
  3046. }
  3047. }
  3048. /***
  3049. Easing Functions
  3050. ================
  3051. ***/
  3052. class Ease extends Stepper {
  3053. constructor(fn = timeline.ease) {
  3054. super();
  3055. this.ease = easing[fn] || fn;
  3056. }
  3057. step(from, to, pos) {
  3058. if (typeof from !== 'number') {
  3059. return pos < 1 ? from : to;
  3060. }
  3061. return from + (to - from) * this.ease(pos);
  3062. }
  3063. }
  3064. /***
  3065. Controller Types
  3066. ================
  3067. ***/
  3068. class Controller extends Stepper {
  3069. constructor(fn) {
  3070. super();
  3071. this.stepper = fn;
  3072. }
  3073. done(c) {
  3074. return c.done;
  3075. }
  3076. step(current, target, dt, c) {
  3077. return this.stepper(current, target, dt, c);
  3078. }
  3079. }
  3080. function recalculate() {
  3081. // Apply the default parameters
  3082. const duration = (this._duration || 500) / 1000;
  3083. const overshoot = this._overshoot || 0; // Calculate the PID natural response
  3084. const eps = 1e-10;
  3085. const pi = Math.PI;
  3086. const os = Math.log(overshoot / 100 + eps);
  3087. const zeta = -os / Math.sqrt(pi * pi + os * os);
  3088. const wn = 3.9 / (zeta * duration); // Calculate the Spring values
  3089. this.d = 2 * zeta * wn;
  3090. this.k = wn * wn;
  3091. }
  3092. class Spring extends Controller {
  3093. constructor(duration = 500, overshoot = 0) {
  3094. super();
  3095. this.duration(duration).overshoot(overshoot);
  3096. }
  3097. step(current, target, dt, c) {
  3098. if (typeof current === 'string') return current;
  3099. c.done = dt === Infinity;
  3100. if (dt === Infinity) return target;
  3101. if (dt === 0) return current;
  3102. if (dt > 100) dt = 16;
  3103. dt /= 1000; // Get the previous velocity
  3104. const velocity = c.velocity || 0; // Apply the control to get the new position and store it
  3105. const acceleration = -this.d * velocity - this.k * (current - target);
  3106. const newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity
  3107. c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value
  3108. c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
  3109. return c.done ? target : newPosition;
  3110. }
  3111. }
  3112. extend(Spring, {
  3113. duration: makeSetterGetter('_duration', recalculate),
  3114. overshoot: makeSetterGetter('_overshoot', recalculate)
  3115. });
  3116. class PID extends Controller {
  3117. constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) {
  3118. super();
  3119. this.p(p).i(i).d(d).windup(windup);
  3120. }
  3121. step(current, target, dt, c) {
  3122. if (typeof current === 'string') return current;
  3123. c.done = dt === Infinity;
  3124. if (dt === Infinity) return target;
  3125. if (dt === 0) return current;
  3126. const p = target - current;
  3127. let i = (c.integral || 0) + p * dt;
  3128. const d = (p - (c.error || 0)) / dt;
  3129. const windup = this._windup; // antiwindup
  3130. if (windup !== false) {
  3131. i = Math.max(-windup, Math.min(i, windup));
  3132. }
  3133. c.error = p;
  3134. c.integral = i;
  3135. c.done = Math.abs(p) < 0.001;
  3136. return c.done ? target : current + (this.P * p + this.I * i + this.D * d);
  3137. }
  3138. }
  3139. extend(PID, {
  3140. windup: makeSetterGetter('_windup'),
  3141. p: makeSetterGetter('P'),
  3142. i: makeSetterGetter('I'),
  3143. d: makeSetterGetter('D')
  3144. });
  3145. const segmentParameters = {
  3146. M: 2,
  3147. L: 2,
  3148. H: 1,
  3149. V: 1,
  3150. C: 6,
  3151. S: 4,
  3152. Q: 4,
  3153. T: 2,
  3154. A: 7,
  3155. Z: 0
  3156. };
  3157. const pathHandlers = {
  3158. M: function (c, p, p0) {
  3159. p.x = p0.x = c[0];
  3160. p.y = p0.y = c[1];
  3161. return ['M', p.x, p.y];
  3162. },
  3163. L: function (c, p) {
  3164. p.x = c[0];
  3165. p.y = c[1];
  3166. return ['L', c[0], c[1]];
  3167. },
  3168. H: function (c, p) {
  3169. p.x = c[0];
  3170. return ['H', c[0]];
  3171. },
  3172. V: function (c, p) {
  3173. p.y = c[0];
  3174. return ['V', c[0]];
  3175. },
  3176. C: function (c, p) {
  3177. p.x = c[4];
  3178. p.y = c[5];
  3179. return ['C', c[0], c[1], c[2], c[3], c[4], c[5]];
  3180. },
  3181. S: function (c, p) {
  3182. p.x = c[2];
  3183. p.y = c[3];
  3184. return ['S', c[0], c[1], c[2], c[3]];
  3185. },
  3186. Q: function (c, p) {
  3187. p.x = c[2];
  3188. p.y = c[3];
  3189. return ['Q', c[0], c[1], c[2], c[3]];
  3190. },
  3191. T: function (c, p) {
  3192. p.x = c[0];
  3193. p.y = c[1];
  3194. return ['T', c[0], c[1]];
  3195. },
  3196. Z: function (c, p, p0) {
  3197. p.x = p0.x;
  3198. p.y = p0.y;
  3199. return ['Z'];
  3200. },
  3201. A: function (c, p) {
  3202. p.x = c[5];
  3203. p.y = c[6];
  3204. return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
  3205. }
  3206. };
  3207. const mlhvqtcsaz = 'mlhvqtcsaz'.split('');
  3208. for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
  3209. pathHandlers[mlhvqtcsaz[i]] = function (i) {
  3210. return function (c, p, p0) {
  3211. if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
  3212. c[5] = c[5] + p.x;
  3213. c[6] = c[6] + p.y;
  3214. } else {
  3215. for (let j = 0, jl = c.length; j < jl; ++j) {
  3216. c[j] = c[j] + (j % 2 ? p.y : p.x);
  3217. }
  3218. }
  3219. return pathHandlers[i](c, p, p0);
  3220. };
  3221. }(mlhvqtcsaz[i].toUpperCase());
  3222. }
  3223. function makeAbsolut(parser) {
  3224. const command = parser.segment[0];
  3225. return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0);
  3226. }
  3227. function segmentComplete(parser) {
  3228. return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()];
  3229. }
  3230. function startNewSegment(parser, token) {
  3231. parser.inNumber && finalizeNumber(parser, false);
  3232. const pathLetter = isPathLetter.test(token);
  3233. if (pathLetter) {
  3234. parser.segment = [token];
  3235. } else {
  3236. const lastCommand = parser.lastCommand;
  3237. const small = lastCommand.toLowerCase();
  3238. const isSmall = lastCommand === small;
  3239. parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand];
  3240. }
  3241. parser.inSegment = true;
  3242. parser.lastCommand = parser.segment[0];
  3243. return pathLetter;
  3244. }
  3245. function finalizeNumber(parser, inNumber) {
  3246. if (!parser.inNumber) throw new Error('Parser Error');
  3247. parser.number && parser.segment.push(parseFloat(parser.number));
  3248. parser.inNumber = inNumber;
  3249. parser.number = '';
  3250. parser.pointSeen = false;
  3251. parser.hasExponent = false;
  3252. if (segmentComplete(parser)) {
  3253. finalizeSegment(parser);
  3254. }
  3255. }
  3256. function finalizeSegment(parser) {
  3257. parser.inSegment = false;
  3258. if (parser.absolute) {
  3259. parser.segment = makeAbsolut(parser);
  3260. }
  3261. parser.segments.push(parser.segment);
  3262. }
  3263. function isArcFlag(parser) {
  3264. if (!parser.segment.length) return false;
  3265. const isArc = parser.segment[0].toUpperCase() === 'A';
  3266. const length = parser.segment.length;
  3267. return isArc && (length === 4 || length === 5);
  3268. }
  3269. function isExponential(parser) {
  3270. return parser.lastToken.toUpperCase() === 'E';
  3271. }
  3272. function pathParser(d, toAbsolute = true) {
  3273. let index = 0;
  3274. let token = '';
  3275. const parser = {
  3276. segment: [],
  3277. inNumber: false,
  3278. number: '',
  3279. lastToken: '',
  3280. inSegment: false,
  3281. segments: [],
  3282. pointSeen: false,
  3283. hasExponent: false,
  3284. absolute: toAbsolute,
  3285. p0: new Point(),
  3286. p: new Point()
  3287. };
  3288. while (parser.lastToken = token, token = d.charAt(index++)) {
  3289. if (!parser.inSegment) {
  3290. if (startNewSegment(parser, token)) {
  3291. continue;
  3292. }
  3293. }
  3294. if (token === '.') {
  3295. if (parser.pointSeen || parser.hasExponent) {
  3296. finalizeNumber(parser, false);
  3297. --index;
  3298. continue;
  3299. }
  3300. parser.inNumber = true;
  3301. parser.pointSeen = true;
  3302. parser.number += token;
  3303. continue;
  3304. }
  3305. if (!isNaN(parseInt(token))) {
  3306. if (parser.number === '0' || isArcFlag(parser)) {
  3307. parser.inNumber = true;
  3308. parser.number = token;
  3309. finalizeNumber(parser, true);
  3310. continue;
  3311. }
  3312. parser.inNumber = true;
  3313. parser.number += token;
  3314. continue;
  3315. }
  3316. if (token === ' ' || token === ',') {
  3317. if (parser.inNumber) {
  3318. finalizeNumber(parser, false);
  3319. }
  3320. continue;
  3321. }
  3322. if (token === '-') {
  3323. if (parser.inNumber && !isExponential(parser)) {
  3324. finalizeNumber(parser, false);
  3325. --index;
  3326. continue;
  3327. }
  3328. parser.number += token;
  3329. parser.inNumber = true;
  3330. continue;
  3331. }
  3332. if (token.toUpperCase() === 'E') {
  3333. parser.number += token;
  3334. parser.hasExponent = true;
  3335. continue;
  3336. }
  3337. if (isPathLetter.test(token)) {
  3338. if (parser.inNumber) {
  3339. finalizeNumber(parser, false);
  3340. } else if (!segmentComplete(parser)) {
  3341. throw new Error('parser Error');
  3342. } else {
  3343. finalizeSegment(parser);
  3344. }
  3345. --index;
  3346. }
  3347. }
  3348. if (parser.inNumber) {
  3349. finalizeNumber(parser, false);
  3350. }
  3351. if (parser.inSegment && segmentComplete(parser)) {
  3352. finalizeSegment(parser);
  3353. }
  3354. return parser.segments;
  3355. }
  3356. function arrayToString(a) {
  3357. let s = '';
  3358. for (let i = 0, il = a.length; i < il; i++) {
  3359. s += a[i][0];
  3360. if (a[i][1] != null) {
  3361. s += a[i][1];
  3362. if (a[i][2] != null) {
  3363. s += ' ';
  3364. s += a[i][2];
  3365. if (a[i][3] != null) {
  3366. s += ' ';
  3367. s += a[i][3];
  3368. s += ' ';
  3369. s += a[i][4];
  3370. if (a[i][5] != null) {
  3371. s += ' ';
  3372. s += a[i][5];
  3373. s += ' ';
  3374. s += a[i][6];
  3375. if (a[i][7] != null) {
  3376. s += ' ';
  3377. s += a[i][7];
  3378. }
  3379. }
  3380. }
  3381. }
  3382. }
  3383. }
  3384. return s + ' ';
  3385. }
  3386. class PathArray extends SVGArray {
  3387. // Get bounding box of path
  3388. bbox() {
  3389. parser().path.setAttribute('d', this.toString());
  3390. return new Box(parser.nodes.path.getBBox());
  3391. } // Move path string
  3392. move(x, y) {
  3393. // get bounding box of current situation
  3394. const box = this.bbox(); // get relative offset
  3395. x -= box.x;
  3396. y -= box.y;
  3397. if (!isNaN(x) && !isNaN(y)) {
  3398. // move every point
  3399. for (let l, i = this.length - 1; i >= 0; i--) {
  3400. l = this[i][0];
  3401. if (l === 'M' || l === 'L' || l === 'T') {
  3402. this[i][1] += x;
  3403. this[i][2] += y;
  3404. } else if (l === 'H') {
  3405. this[i][1] += x;
  3406. } else if (l === 'V') {
  3407. this[i][1] += y;
  3408. } else if (l === 'C' || l === 'S' || l === 'Q') {
  3409. this[i][1] += x;
  3410. this[i][2] += y;
  3411. this[i][3] += x;
  3412. this[i][4] += y;
  3413. if (l === 'C') {
  3414. this[i][5] += x;
  3415. this[i][6] += y;
  3416. }
  3417. } else if (l === 'A') {
  3418. this[i][6] += x;
  3419. this[i][7] += y;
  3420. }
  3421. }
  3422. }
  3423. return this;
  3424. } // Absolutize and parse path to array
  3425. parse(d = 'M0 0') {
  3426. if (Array.isArray(d)) {
  3427. d = Array.prototype.concat.apply([], d).toString();
  3428. }
  3429. return pathParser(d);
  3430. } // Resize path string
  3431. size(width, height) {
  3432. // get bounding box of current situation
  3433. const box = this.bbox();
  3434. let i, l; // If the box width or height is 0 then we ignore
  3435. // transformations on the respective axis
  3436. box.width = box.width === 0 ? 1 : box.width;
  3437. box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size
  3438. for (i = this.length - 1; i >= 0; i--) {
  3439. l = this[i][0];
  3440. if (l === 'M' || l === 'L' || l === 'T') {
  3441. this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
  3442. this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
  3443. } else if (l === 'H') {
  3444. this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
  3445. } else if (l === 'V') {
  3446. this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
  3447. } else if (l === 'C' || l === 'S' || l === 'Q') {
  3448. this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
  3449. this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
  3450. this[i][3] = (this[i][3] - box.x) * width / box.width + box.x;
  3451. this[i][4] = (this[i][4] - box.y) * height / box.height + box.y;
  3452. if (l === 'C') {
  3453. this[i][5] = (this[i][5] - box.x) * width / box.width + box.x;
  3454. this[i][6] = (this[i][6] - box.y) * height / box.height + box.y;
  3455. }
  3456. } else if (l === 'A') {
  3457. // resize radii
  3458. this[i][1] = this[i][1] * width / box.width;
  3459. this[i][2] = this[i][2] * height / box.height; // move position values
  3460. this[i][6] = (this[i][6] - box.x) * width / box.width + box.x;
  3461. this[i][7] = (this[i][7] - box.y) * height / box.height + box.y;
  3462. }
  3463. }
  3464. return this;
  3465. } // Convert array to string
  3466. toString() {
  3467. return arrayToString(this);
  3468. }
  3469. }
  3470. const getClassForType = value => {
  3471. const type = typeof value;
  3472. if (type === 'number') {
  3473. return SVGNumber;
  3474. } else if (type === 'string') {
  3475. if (Color.isColor(value)) {
  3476. return Color;
  3477. } else if (delimiter.test(value)) {
  3478. return isPathLetter.test(value) ? PathArray : SVGArray;
  3479. } else if (numberAndUnit.test(value)) {
  3480. return SVGNumber;
  3481. } else {
  3482. return NonMorphable;
  3483. }
  3484. } else if (morphableTypes.indexOf(value.constructor) > -1) {
  3485. return value.constructor;
  3486. } else if (Array.isArray(value)) {
  3487. return SVGArray;
  3488. } else if (type === 'object') {
  3489. return ObjectBag;
  3490. } else {
  3491. return NonMorphable;
  3492. }
  3493. };
  3494. class Morphable {
  3495. constructor(stepper) {
  3496. this._stepper = stepper || new Ease('-');
  3497. this._from = null;
  3498. this._to = null;
  3499. this._type = null;
  3500. this._context = null;
  3501. this._morphObj = null;
  3502. }
  3503. at(pos) {
  3504. return this._morphObj.morph(this._from, this._to, pos, this._stepper, this._context);
  3505. }
  3506. done() {
  3507. const complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
  3508. return last && curr;
  3509. }, true);
  3510. return complete;
  3511. }
  3512. from(val) {
  3513. if (val == null) {
  3514. return this._from;
  3515. }
  3516. this._from = this._set(val);
  3517. return this;
  3518. }
  3519. stepper(stepper) {
  3520. if (stepper == null) return this._stepper;
  3521. this._stepper = stepper;
  3522. return this;
  3523. }
  3524. to(val) {
  3525. if (val == null) {
  3526. return this._to;
  3527. }
  3528. this._to = this._set(val);
  3529. return this;
  3530. }
  3531. type(type) {
  3532. // getter
  3533. if (type == null) {
  3534. return this._type;
  3535. } // setter
  3536. this._type = type;
  3537. return this;
  3538. }
  3539. _set(value) {
  3540. if (!this._type) {
  3541. this.type(getClassForType(value));
  3542. }
  3543. let result = new this._type(value);
  3544. if (this._type === Color) {
  3545. result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result;
  3546. }
  3547. if (this._type === ObjectBag) {
  3548. result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result;
  3549. }
  3550. result = result.toConsumable();
  3551. this._morphObj = this._morphObj || new this._type();
  3552. this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) {
  3553. o.done = true;
  3554. return o;
  3555. });
  3556. return result;
  3557. }
  3558. }
  3559. class NonMorphable {
  3560. constructor(...args) {
  3561. this.init(...args);
  3562. }
  3563. init(val) {
  3564. val = Array.isArray(val) ? val[0] : val;
  3565. this.value = val;
  3566. return this;
  3567. }
  3568. toArray() {
  3569. return [this.value];
  3570. }
  3571. valueOf() {
  3572. return this.value;
  3573. }
  3574. }
  3575. class TransformBag {
  3576. constructor(...args) {
  3577. this.init(...args);
  3578. }
  3579. init(obj) {
  3580. if (Array.isArray(obj)) {
  3581. obj = {
  3582. scaleX: obj[0],
  3583. scaleY: obj[1],
  3584. shear: obj[2],
  3585. rotate: obj[3],
  3586. translateX: obj[4],
  3587. translateY: obj[5],
  3588. originX: obj[6],
  3589. originY: obj[7]
  3590. };
  3591. }
  3592. Object.assign(this, TransformBag.defaults, obj);
  3593. return this;
  3594. }
  3595. toArray() {
  3596. const v = this;
  3597. return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
  3598. }
  3599. }
  3600. TransformBag.defaults = {
  3601. scaleX: 1,
  3602. scaleY: 1,
  3603. shear: 0,
  3604. rotate: 0,
  3605. translateX: 0,
  3606. translateY: 0,
  3607. originX: 0,
  3608. originY: 0
  3609. };
  3610. const sortByKey = (a, b) => {
  3611. return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
  3612. };
  3613. class ObjectBag {
  3614. constructor(...args) {
  3615. this.init(...args);
  3616. }
  3617. align(other) {
  3618. const values = this.values;
  3619. for (let i = 0, il = values.length; i < il; ++i) {
  3620. // If the type is the same we only need to check if the color is in the correct format
  3621. if (values[i + 1] === other[i + 1]) {
  3622. if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) {
  3623. const space = other[i + 7];
  3624. const color = new Color(this.values.splice(i + 3, 5))[space]().toArray();
  3625. this.values.splice(i + 3, 0, ...color);
  3626. }
  3627. i += values[i + 2] + 2;
  3628. continue;
  3629. }
  3630. if (!other[i + 1]) {
  3631. return this;
  3632. } // The types differ, so we overwrite the new type with the old one
  3633. // And initialize it with the types default (e.g. black for color or 0 for number)
  3634. const defaultObject = new other[i + 1]().toArray(); // Than we fix the values array
  3635. const toDelete = values[i + 2] + 3;
  3636. values.splice(i, toDelete, other[i], other[i + 1], other[i + 2], ...defaultObject);
  3637. i += values[i + 2] + 2;
  3638. }
  3639. return this;
  3640. }
  3641. init(objOrArr) {
  3642. this.values = [];
  3643. if (Array.isArray(objOrArr)) {
  3644. this.values = objOrArr.slice();
  3645. return;
  3646. }
  3647. objOrArr = objOrArr || {};
  3648. const entries = [];
  3649. for (const i in objOrArr) {
  3650. const Type = getClassForType(objOrArr[i]);
  3651. const val = new Type(objOrArr[i]).toArray();
  3652. entries.push([i, Type, val.length, ...val]);
  3653. }
  3654. entries.sort(sortByKey);
  3655. this.values = entries.reduce((last, curr) => last.concat(curr), []);
  3656. return this;
  3657. }
  3658. toArray() {
  3659. return this.values;
  3660. }
  3661. valueOf() {
  3662. const obj = {};
  3663. const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) {
  3664. while (arr.length) {
  3665. const key = arr.shift();
  3666. const Type = arr.shift();
  3667. const num = arr.shift();
  3668. const values = arr.splice(0, num);
  3669. obj[key] = new Type(values); // .valueOf()
  3670. }
  3671. return obj;
  3672. }
  3673. }
  3674. const morphableTypes = [NonMorphable, TransformBag, ObjectBag];
  3675. function registerMorphableType(type = []) {
  3676. morphableTypes.push(...[].concat(type));
  3677. }
  3678. function makeMorphable() {
  3679. extend(morphableTypes, {
  3680. to(val) {
  3681. return new Morphable().type(this.constructor).from(this.toArray()) // this.valueOf())
  3682. .to(val);
  3683. },
  3684. fromArray(arr) {
  3685. this.init(arr);
  3686. return this;
  3687. },
  3688. toConsumable() {
  3689. return this.toArray();
  3690. },
  3691. morph(from, to, pos, stepper, context) {
  3692. const mapper = function (i, index) {
  3693. return stepper.step(i, to[index], pos, context[index], context);
  3694. };
  3695. return this.fromArray(from.map(mapper));
  3696. }
  3697. });
  3698. }
  3699. class Path extends Shape {
  3700. // Initialize node
  3701. constructor(node, attrs = node) {
  3702. super(nodeOrNew('path', node), attrs);
  3703. } // Get array
  3704. array() {
  3705. return this._array || (this._array = new PathArray(this.attr('d')));
  3706. } // Clear array cache
  3707. clear() {
  3708. delete this._array;
  3709. return this;
  3710. } // Set height of element
  3711. height(height) {
  3712. return height == null ? this.bbox().height : this.size(this.bbox().width, height);
  3713. } // Move by left top corner
  3714. move(x, y) {
  3715. return this.attr('d', this.array().move(x, y));
  3716. } // Plot new path
  3717. plot(d) {
  3718. return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
  3719. } // Set element size to given width and height
  3720. size(width, height) {
  3721. const p = proportionalSize(this, width, height);
  3722. return this.attr('d', this.array().size(p.width, p.height));
  3723. } // Set width of element
  3724. width(width) {
  3725. return width == null ? this.bbox().width : this.size(width, this.bbox().height);
  3726. } // Move by left top corner over x-axis
  3727. x(x) {
  3728. return x == null ? this.bbox().x : this.move(x, this.bbox().y);
  3729. } // Move by left top corner over y-axis
  3730. y(y) {
  3731. return y == null ? this.bbox().y : this.move(this.bbox().x, y);
  3732. }
  3733. } // Define morphable array
  3734. Path.prototype.MorphArray = PathArray; // Add parent method
  3735. registerMethods({
  3736. Container: {
  3737. // Create a wrapped path element
  3738. path: wrapWithAttrCheck(function (d) {
  3739. // make sure plot is called as a setter
  3740. return this.put(new Path()).plot(d || new PathArray());
  3741. })
  3742. }
  3743. });
  3744. register(Path, 'Path');
  3745. function array() {
  3746. return this._array || (this._array = new PointArray(this.attr('points')));
  3747. } // Clear array cache
  3748. function clear() {
  3749. delete this._array;
  3750. return this;
  3751. } // Move by left top corner
  3752. function move$2(x, y) {
  3753. return this.attr('points', this.array().move(x, y));
  3754. } // Plot new path
  3755. function plot(p) {
  3756. return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
  3757. } // Set element size to given width and height
  3758. function size$1(width, height) {
  3759. const p = proportionalSize(this, width, height);
  3760. return this.attr('points', this.array().size(p.width, p.height));
  3761. }
  3762. var poly = {
  3763. __proto__: null,
  3764. array: array,
  3765. clear: clear,
  3766. move: move$2,
  3767. plot: plot,
  3768. size: size$1
  3769. };
  3770. class Polygon extends Shape {
  3771. // Initialize node
  3772. constructor(node, attrs = node) {
  3773. super(nodeOrNew('polygon', node), attrs);
  3774. }
  3775. }
  3776. registerMethods({
  3777. Container: {
  3778. // Create a wrapped polygon element
  3779. polygon: wrapWithAttrCheck(function (p) {
  3780. // make sure plot is called as a setter
  3781. return this.put(new Polygon()).plot(p || new PointArray());
  3782. })
  3783. }
  3784. });
  3785. extend(Polygon, pointed);
  3786. extend(Polygon, poly);
  3787. register(Polygon, 'Polygon');
  3788. class Polyline extends Shape {
  3789. // Initialize node
  3790. constructor(node, attrs = node) {
  3791. super(nodeOrNew('polyline', node), attrs);
  3792. }
  3793. }
  3794. registerMethods({
  3795. Container: {
  3796. // Create a wrapped polygon element
  3797. polyline: wrapWithAttrCheck(function (p) {
  3798. // make sure plot is called as a setter
  3799. return this.put(new Polyline()).plot(p || new PointArray());
  3800. })
  3801. }
  3802. });
  3803. extend(Polyline, pointed);
  3804. extend(Polyline, poly);
  3805. register(Polyline, 'Polyline');
  3806. class Rect extends Shape {
  3807. // Initialize node
  3808. constructor(node, attrs = node) {
  3809. super(nodeOrNew('rect', node), attrs);
  3810. }
  3811. }
  3812. extend(Rect, {
  3813. rx,
  3814. ry
  3815. });
  3816. registerMethods({
  3817. Container: {
  3818. // Create a rect element
  3819. rect: wrapWithAttrCheck(function (width, height) {
  3820. return this.put(new Rect()).size(width, height);
  3821. })
  3822. }
  3823. });
  3824. register(Rect, 'Rect');
  3825. class Queue {
  3826. constructor() {
  3827. this._first = null;
  3828. this._last = null;
  3829. } // Shows us the first item in the list
  3830. first() {
  3831. return this._first && this._first.value;
  3832. } // Shows us the last item in the list
  3833. last() {
  3834. return this._last && this._last.value;
  3835. }
  3836. push(value) {
  3837. // An item stores an id and the provided value
  3838. const item = typeof value.next !== 'undefined' ? value : {
  3839. value: value,
  3840. next: null,
  3841. prev: null
  3842. }; // Deal with the queue being empty or populated
  3843. if (this._last) {
  3844. item.prev = this._last;
  3845. this._last.next = item;
  3846. this._last = item;
  3847. } else {
  3848. this._last = item;
  3849. this._first = item;
  3850. } // Return the current item
  3851. return item;
  3852. } // Removes the item that was returned from the push
  3853. remove(item) {
  3854. // Relink the previous item
  3855. if (item.prev) item.prev.next = item.next;
  3856. if (item.next) item.next.prev = item.prev;
  3857. if (item === this._last) this._last = item.prev;
  3858. if (item === this._first) this._first = item.next; // Invalidate item
  3859. item.prev = null;
  3860. item.next = null;
  3861. }
  3862. shift() {
  3863. // Check if we have a value
  3864. const remove = this._first;
  3865. if (!remove) return null; // If we do, remove it and relink things
  3866. this._first = remove.next;
  3867. if (this._first) this._first.prev = null;
  3868. this._last = this._first ? this._last : null;
  3869. return remove.value;
  3870. }
  3871. }
  3872. const Animator = {
  3873. nextDraw: null,
  3874. frames: new Queue(),
  3875. timeouts: new Queue(),
  3876. immediates: new Queue(),
  3877. timer: () => globals.window.performance || globals.window.Date,
  3878. transforms: [],
  3879. frame(fn) {
  3880. // Store the node
  3881. const node = Animator.frames.push({
  3882. run: fn
  3883. }); // Request an animation frame if we don't have one
  3884. if (Animator.nextDraw === null) {
  3885. Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
  3886. } // Return the node so we can remove it easily
  3887. return node;
  3888. },
  3889. timeout(fn, delay) {
  3890. delay = delay || 0; // Work out when the event should fire
  3891. const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue
  3892. const node = Animator.timeouts.push({
  3893. run: fn,
  3894. time: time
  3895. }); // Request another animation frame if we need one
  3896. if (Animator.nextDraw === null) {
  3897. Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
  3898. }
  3899. return node;
  3900. },
  3901. immediate(fn) {
  3902. // Add the immediate fn to the end of the queue
  3903. const node = Animator.immediates.push(fn); // Request another animation frame if we need one
  3904. if (Animator.nextDraw === null) {
  3905. Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
  3906. }
  3907. return node;
  3908. },
  3909. cancelFrame(node) {
  3910. node != null && Animator.frames.remove(node);
  3911. },
  3912. clearTimeout(node) {
  3913. node != null && Animator.timeouts.remove(node);
  3914. },
  3915. cancelImmediate(node) {
  3916. node != null && Animator.immediates.remove(node);
  3917. },
  3918. _draw(now) {
  3919. // Run all the timeouts we can run, if they are not ready yet, add them
  3920. // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
  3921. let nextTimeout = null;
  3922. const lastTimeout = Animator.timeouts.last();
  3923. while (nextTimeout = Animator.timeouts.shift()) {
  3924. // Run the timeout if its time, or push it to the end
  3925. if (now >= nextTimeout.time) {
  3926. nextTimeout.run();
  3927. } else {
  3928. Animator.timeouts.push(nextTimeout);
  3929. } // If we hit the last item, we should stop shifting out more items
  3930. if (nextTimeout === lastTimeout) break;
  3931. } // Run all of the animation frames
  3932. let nextFrame = null;
  3933. const lastFrame = Animator.frames.last();
  3934. while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
  3935. nextFrame.run(now);
  3936. }
  3937. let nextImmediate = null;
  3938. while (nextImmediate = Animator.immediates.shift()) {
  3939. nextImmediate();
  3940. } // If we have remaining timeouts or frames, draw until we don't anymore
  3941. Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;
  3942. }
  3943. };
  3944. const makeSchedule = function (runnerInfo) {
  3945. const start = runnerInfo.start;
  3946. const duration = runnerInfo.runner.duration();
  3947. const end = start + duration;
  3948. return {
  3949. start: start,
  3950. duration: duration,
  3951. end: end,
  3952. runner: runnerInfo.runner
  3953. };
  3954. };
  3955. const defaultSource = function () {
  3956. const w = globals.window;
  3957. return (w.performance || w.Date).now();
  3958. };
  3959. class Timeline extends EventTarget {
  3960. // Construct a new timeline on the given element
  3961. constructor(timeSource = defaultSource) {
  3962. super();
  3963. this._timeSource = timeSource; // Store the timing variables
  3964. this._startTime = 0;
  3965. this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false
  3966. this._persist = 0; // Keep track of the running animations and their starting parameters
  3967. this._nextFrame = null;
  3968. this._paused = true;
  3969. this._runners = [];
  3970. this._runnerIds = [];
  3971. this._lastRunnerId = -1;
  3972. this._time = 0;
  3973. this._lastSourceTime = 0;
  3974. this._lastStepTime = 0; // Make sure that step is always called in class context
  3975. this._step = this._stepFn.bind(this, false);
  3976. this._stepImmediate = this._stepFn.bind(this, true);
  3977. }
  3978. active() {
  3979. return !!this._nextFrame;
  3980. }
  3981. finish() {
  3982. // Go to end and pause
  3983. this.time(this.getEndTimeOfTimeline() + 1);
  3984. return this.pause();
  3985. } // Calculates the end of the timeline
  3986. getEndTime() {
  3987. const lastRunnerInfo = this.getLastRunnerInfo();
  3988. const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0;
  3989. const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
  3990. return lastStartTime + lastDuration;
  3991. }
  3992. getEndTimeOfTimeline() {
  3993. const endTimes = this._runners.map(i => i.start + i.runner.duration());
  3994. return Math.max(0, ...endTimes);
  3995. }
  3996. getLastRunnerInfo() {
  3997. return this.getRunnerInfoById(this._lastRunnerId);
  3998. }
  3999. getRunnerInfoById(id) {
  4000. return this._runners[this._runnerIds.indexOf(id)] || null;
  4001. }
  4002. pause() {
  4003. this._paused = true;
  4004. return this._continue();
  4005. }
  4006. persist(dtOrForever) {
  4007. if (dtOrForever == null) return this._persist;
  4008. this._persist = dtOrForever;
  4009. return this;
  4010. }
  4011. play() {
  4012. // Now make sure we are not paused and continue the animation
  4013. this._paused = false;
  4014. return this.updateTime()._continue();
  4015. }
  4016. reverse(yes) {
  4017. const currentSpeed = this.speed();
  4018. if (yes == null) return this.speed(-currentSpeed);
  4019. const positive = Math.abs(currentSpeed);
  4020. return this.speed(yes ? -positive : positive);
  4021. } // schedules a runner on the timeline
  4022. schedule(runner, delay, when) {
  4023. if (runner == null) {
  4024. return this._runners.map(makeSchedule);
  4025. } // The start time for the next animation can either be given explicitly,
  4026. // derived from the current timeline time or it can be relative to the
  4027. // last start time to chain animations directly
  4028. let absoluteStartTime = 0;
  4029. const endTime = this.getEndTime();
  4030. delay = delay || 0; // Work out when to start the animation
  4031. if (when == null || when === 'last' || when === 'after') {
  4032. // Take the last time and increment
  4033. absoluteStartTime = endTime;
  4034. } else if (when === 'absolute' || when === 'start') {
  4035. absoluteStartTime = delay;
  4036. delay = 0;
  4037. } else if (when === 'now') {
  4038. absoluteStartTime = this._time;
  4039. } else if (when === 'relative') {
  4040. const runnerInfo = this.getRunnerInfoById(runner.id);
  4041. if (runnerInfo) {
  4042. absoluteStartTime = runnerInfo.start + delay;
  4043. delay = 0;
  4044. }
  4045. } else if (when === 'with-last') {
  4046. const lastRunnerInfo = this.getLastRunnerInfo();
  4047. const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
  4048. absoluteStartTime = lastStartTime;
  4049. } else {
  4050. throw new Error('Invalid value for the "when" parameter');
  4051. } // Manage runner
  4052. runner.unschedule();
  4053. runner.timeline(this);
  4054. const persist = runner.persist();
  4055. const runnerInfo = {
  4056. persist: persist === null ? this._persist : persist,
  4057. start: absoluteStartTime + delay,
  4058. runner
  4059. };
  4060. this._lastRunnerId = runner.id;
  4061. this._runners.push(runnerInfo);
  4062. this._runners.sort((a, b) => a.start - b.start);
  4063. this._runnerIds = this._runners.map(info => info.runner.id);
  4064. this.updateTime()._continue();
  4065. return this;
  4066. }
  4067. seek(dt) {
  4068. return this.time(this._time + dt);
  4069. }
  4070. source(fn) {
  4071. if (fn == null) return this._timeSource;
  4072. this._timeSource = fn;
  4073. return this;
  4074. }
  4075. speed(speed) {
  4076. if (speed == null) return this._speed;
  4077. this._speed = speed;
  4078. return this;
  4079. }
  4080. stop() {
  4081. // Go to start and pause
  4082. this.time(0);
  4083. return this.pause();
  4084. }
  4085. time(time) {
  4086. if (time == null) return this._time;
  4087. this._time = time;
  4088. return this._continue(true);
  4089. } // Remove the runner from this timeline
  4090. unschedule(runner) {
  4091. const index = this._runnerIds.indexOf(runner.id);
  4092. if (index < 0) return this;
  4093. this._runners.splice(index, 1);
  4094. this._runnerIds.splice(index, 1);
  4095. runner.timeline(null);
  4096. return this;
  4097. } // Makes sure, that after pausing the time doesn't jump
  4098. updateTime() {
  4099. if (!this.active()) {
  4100. this._lastSourceTime = this._timeSource();
  4101. }
  4102. return this;
  4103. } // Checks if we are running and continues the animation
  4104. _continue(immediateStep = false) {
  4105. Animator.cancelFrame(this._nextFrame);
  4106. this._nextFrame = null;
  4107. if (immediateStep) return this._stepImmediate();
  4108. if (this._paused) return this;
  4109. this._nextFrame = Animator.frame(this._step);
  4110. return this;
  4111. }
  4112. _stepFn(immediateStep = false) {
  4113. // Get the time delta from the last time and update the time
  4114. const time = this._timeSource();
  4115. let dtSource = time - this._lastSourceTime;
  4116. if (immediateStep) dtSource = 0;
  4117. const dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
  4118. this._lastSourceTime = time; // Only update the time if we use the timeSource.
  4119. // Otherwise use the current time
  4120. if (!immediateStep) {
  4121. // Update the time
  4122. this._time += dtTime;
  4123. this._time = this._time < 0 ? 0 : this._time;
  4124. }
  4125. this._lastStepTime = this._time;
  4126. this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time
  4127. // is now before the startTime of the runner. That is why we need to set
  4128. // the runner to position 0
  4129. // FIXME:
  4130. // However, resetting in insertion order leads to bugs. Considering the case,
  4131. // where 2 runners change the same attribute but in different times,
  4132. // resetting both of them will lead to the case where the later defined
  4133. // runner always wins the reset even if the other runner started earlier
  4134. // and therefore should win the attribute battle
  4135. // this can be solved by resetting them backwards
  4136. for (let k = this._runners.length; k--;) {
  4137. // Get and run the current runner and ignore it if its inactive
  4138. const runnerInfo = this._runners[k];
  4139. const runner = runnerInfo.runner; // Make sure that we give the actual difference
  4140. // between runner start time and now
  4141. const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
  4142. // and try to reset it
  4143. if (dtToStart <= 0) {
  4144. runner.reset();
  4145. }
  4146. } // Run all of the runners directly
  4147. let runnersLeft = false;
  4148. for (let i = 0, len = this._runners.length; i < len; i++) {
  4149. // Get and run the current runner and ignore it if its inactive
  4150. const runnerInfo = this._runners[i];
  4151. const runner = runnerInfo.runner;
  4152. let dt = dtTime; // Make sure that we give the actual difference
  4153. // between runner start time and now
  4154. const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
  4155. if (dtToStart <= 0) {
  4156. runnersLeft = true;
  4157. continue;
  4158. } else if (dtToStart < dt) {
  4159. // Adjust dt to make sure that animation is on point
  4160. dt = dtToStart;
  4161. }
  4162. if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
  4163. // frame, otherwise, remove the completed runner
  4164. const finished = runner.step(dt).done;
  4165. if (!finished) {
  4166. runnersLeft = true; // continue
  4167. } else if (runnerInfo.persist !== true) {
  4168. // runner is finished. And runner might get removed
  4169. const endTime = runner.duration() - runner.time() + this._time;
  4170. if (endTime + runnerInfo.persist < this._time) {
  4171. // Delete runner and correct index
  4172. runner.unschedule();
  4173. --i;
  4174. --len;
  4175. }
  4176. }
  4177. } // Basically: we continue when there are runners right from us in time
  4178. // when -->, and when runners are left from us when <--
  4179. if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) {
  4180. this._continue();
  4181. } else {
  4182. this.pause();
  4183. this.fire('finished');
  4184. }
  4185. return this;
  4186. }
  4187. }
  4188. registerMethods({
  4189. Element: {
  4190. timeline: function (timeline) {
  4191. if (timeline == null) {
  4192. this._timeline = this._timeline || new Timeline();
  4193. return this._timeline;
  4194. } else {
  4195. this._timeline = timeline;
  4196. return this;
  4197. }
  4198. }
  4199. }
  4200. });
  4201. class Runner extends EventTarget {
  4202. constructor(options) {
  4203. super(); // Store a unique id on the runner, so that we can identify it later
  4204. this.id = Runner.id++; // Ensure a default value
  4205. options = options == null ? timeline.duration : options; // Ensure that we get a controller
  4206. options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables
  4207. this._element = null;
  4208. this._timeline = null;
  4209. this.done = false;
  4210. this._queue = []; // Work out the stepper and the duration
  4211. this._duration = typeof options === 'number' && options;
  4212. this._isDeclarative = options instanceof Controller;
  4213. this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change
  4214. this._history = {}; // Store the state of the runner
  4215. this.enabled = true;
  4216. this._time = 0;
  4217. this._lastTime = 0; // At creation, the runner is in reset state
  4218. this._reseted = true; // Save transforms applied to this runner
  4219. this.transforms = new Matrix();
  4220. this.transformId = 1; // Looping variables
  4221. this._haveReversed = false;
  4222. this._reverse = false;
  4223. this._loopsDone = 0;
  4224. this._swing = false;
  4225. this._wait = 0;
  4226. this._times = 1;
  4227. this._frameId = null; // Stores how long a runner is stored after being done
  4228. this._persist = this._isDeclarative ? true : null;
  4229. }
  4230. static sanitise(duration, delay, when) {
  4231. // Initialise the default parameters
  4232. let times = 1;
  4233. let swing = false;
  4234. let wait = 0;
  4235. duration = duration || timeline.duration;
  4236. delay = delay || timeline.delay;
  4237. when = when || 'last'; // If we have an object, unpack the values
  4238. if (typeof duration === 'object' && !(duration instanceof Stepper)) {
  4239. delay = duration.delay || delay;
  4240. when = duration.when || when;
  4241. swing = duration.swing || swing;
  4242. times = duration.times || times;
  4243. wait = duration.wait || wait;
  4244. duration = duration.duration || timeline.duration;
  4245. }
  4246. return {
  4247. duration: duration,
  4248. delay: delay,
  4249. swing: swing,
  4250. times: times,
  4251. wait: wait,
  4252. when: when
  4253. };
  4254. }
  4255. active(enabled) {
  4256. if (enabled == null) return this.enabled;
  4257. this.enabled = enabled;
  4258. return this;
  4259. }
  4260. /*
  4261. Private Methods
  4262. ===============
  4263. Methods that shouldn't be used externally
  4264. */
  4265. addTransform(transform, index) {
  4266. this.transforms.lmultiplyO(transform);
  4267. return this;
  4268. }
  4269. after(fn) {
  4270. return this.on('finished', fn);
  4271. }
  4272. animate(duration, delay, when) {
  4273. const o = Runner.sanitise(duration, delay, when);
  4274. const runner = new Runner(o.duration);
  4275. if (this._timeline) runner.timeline(this._timeline);
  4276. if (this._element) runner.element(this._element);
  4277. return runner.loop(o).schedule(o.delay, o.when);
  4278. }
  4279. clearTransform() {
  4280. this.transforms = new Matrix();
  4281. return this;
  4282. } // TODO: Keep track of all transformations so that deletion is faster
  4283. clearTransformsFromQueue() {
  4284. if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
  4285. this._queue = this._queue.filter(item => {
  4286. return !item.isTransform;
  4287. });
  4288. }
  4289. }
  4290. delay(delay) {
  4291. return this.animate(0, delay);
  4292. }
  4293. duration() {
  4294. return this._times * (this._wait + this._duration) - this._wait;
  4295. }
  4296. during(fn) {
  4297. return this.queue(null, fn);
  4298. }
  4299. ease(fn) {
  4300. this._stepper = new Ease(fn);
  4301. return this;
  4302. }
  4303. /*
  4304. Runner Definitions
  4305. ==================
  4306. These methods help us define the runtime behaviour of the Runner or they
  4307. help us make new runners from the current runner
  4308. */
  4309. element(element) {
  4310. if (element == null) return this._element;
  4311. this._element = element;
  4312. element._prepareRunner();
  4313. return this;
  4314. }
  4315. finish() {
  4316. return this.step(Infinity);
  4317. }
  4318. loop(times, swing, wait) {
  4319. // Deal with the user passing in an object
  4320. if (typeof times === 'object') {
  4321. swing = times.swing;
  4322. wait = times.wait;
  4323. times = times.times;
  4324. } // Sanitise the values and store them
  4325. this._times = times || Infinity;
  4326. this._swing = swing || false;
  4327. this._wait = wait || 0; // Allow true to be passed
  4328. if (this._times === true) {
  4329. this._times = Infinity;
  4330. }
  4331. return this;
  4332. }
  4333. loops(p) {
  4334. const loopDuration = this._duration + this._wait;
  4335. if (p == null) {
  4336. const loopsDone = Math.floor(this._time / loopDuration);
  4337. const relativeTime = this._time - loopsDone * loopDuration;
  4338. const position = relativeTime / this._duration;
  4339. return Math.min(loopsDone + position, this._times);
  4340. }
  4341. const whole = Math.floor(p);
  4342. const partial = p % 1;
  4343. const time = loopDuration * whole + this._duration * partial;
  4344. return this.time(time);
  4345. }
  4346. persist(dtOrForever) {
  4347. if (dtOrForever == null) return this._persist;
  4348. this._persist = dtOrForever;
  4349. return this;
  4350. }
  4351. position(p) {
  4352. // Get all of the variables we need
  4353. const x = this._time;
  4354. const d = this._duration;
  4355. const w = this._wait;
  4356. const t = this._times;
  4357. const s = this._swing;
  4358. const r = this._reverse;
  4359. let position;
  4360. if (p == null) {
  4361. /*
  4362. This function converts a time to a position in the range [0, 1]
  4363. The full explanation can be found in this desmos demonstration
  4364. https://www.desmos.com/calculator/u4fbavgche
  4365. The logic is slightly simplified here because we can use booleans
  4366. */
  4367. // Figure out the value without thinking about the start or end time
  4368. const f = function (x) {
  4369. const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d));
  4370. const backwards = swinging && !r || !swinging && r;
  4371. const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards;
  4372. const clipped = Math.max(Math.min(uncliped, 1), 0);
  4373. return clipped;
  4374. }; // Figure out the value by incorporating the start time
  4375. const endTime = t * (w + d) - w;
  4376. position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5));
  4377. return position;
  4378. } // Work out the loops done and add the position to the loops done
  4379. const loopsDone = Math.floor(this.loops());
  4380. const swingForward = s && loopsDone % 2 === 0;
  4381. const forwards = swingForward && !r || r && swingForward;
  4382. position = loopsDone + (forwards ? p : 1 - p);
  4383. return this.loops(position);
  4384. }
  4385. progress(p) {
  4386. if (p == null) {
  4387. return Math.min(1, this._time / this.duration());
  4388. }
  4389. return this.time(p * this.duration());
  4390. }
  4391. /*
  4392. Basic Functionality
  4393. ===================
  4394. These methods allow us to attach basic functions to the runner directly
  4395. */
  4396. queue(initFn, runFn, retargetFn, isTransform) {
  4397. this._queue.push({
  4398. initialiser: initFn || noop,
  4399. runner: runFn || noop,
  4400. retarget: retargetFn,
  4401. isTransform: isTransform,
  4402. initialised: false,
  4403. finished: false
  4404. });
  4405. const timeline = this.timeline();
  4406. timeline && this.timeline()._continue();
  4407. return this;
  4408. }
  4409. reset() {
  4410. if (this._reseted) return this;
  4411. this.time(0);
  4412. this._reseted = true;
  4413. return this;
  4414. }
  4415. reverse(reverse) {
  4416. this._reverse = reverse == null ? !this._reverse : reverse;
  4417. return this;
  4418. }
  4419. schedule(timeline, delay, when) {
  4420. // The user doesn't need to pass a timeline if we already have one
  4421. if (!(timeline instanceof Timeline)) {
  4422. when = delay;
  4423. delay = timeline;
  4424. timeline = this.timeline();
  4425. } // If there is no timeline, yell at the user...
  4426. if (!timeline) {
  4427. throw Error('Runner cannot be scheduled without timeline');
  4428. } // Schedule the runner on the timeline provided
  4429. timeline.schedule(this, delay, when);
  4430. return this;
  4431. }
  4432. step(dt) {
  4433. // If we are inactive, this stepper just gets skipped
  4434. if (!this.enabled) return this; // Update the time and get the new position
  4435. dt = dt == null ? 16 : dt;
  4436. this._time += dt;
  4437. const position = this.position(); // Figure out if we need to run the stepper in this frame
  4438. const running = this._lastPosition !== position && this._time >= 0;
  4439. this._lastPosition = position; // Figure out if we just started
  4440. const duration = this.duration();
  4441. const justStarted = this._lastTime <= 0 && this._time > 0;
  4442. const justFinished = this._lastTime < duration && this._time >= duration;
  4443. this._lastTime = this._time;
  4444. if (justStarted) {
  4445. this.fire('start', this);
  4446. } // Work out if the runner is finished set the done flag here so animations
  4447. // know, that they are running in the last step (this is good for
  4448. // transformations which can be merged)
  4449. const declarative = this._isDeclarative;
  4450. this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reset state anymore
  4451. this._reseted = false;
  4452. let converged = false; // Call initialise and the run function
  4453. if (running || declarative) {
  4454. this._initialise(running); // clear the transforms on this runner so they dont get added again and again
  4455. this.transforms = new Matrix();
  4456. converged = this._run(declarative ? dt : position);
  4457. this.fire('step', this);
  4458. } // correct the done flag here
  4459. // declarative animations itself know when they converged
  4460. this.done = this.done || converged && declarative;
  4461. if (justFinished) {
  4462. this.fire('finished', this);
  4463. }
  4464. return this;
  4465. }
  4466. /*
  4467. Runner animation methods
  4468. ========================
  4469. Control how the animation plays
  4470. */
  4471. time(time) {
  4472. if (time == null) {
  4473. return this._time;
  4474. }
  4475. const dt = time - this._time;
  4476. this.step(dt);
  4477. return this;
  4478. }
  4479. timeline(timeline) {
  4480. // check explicitly for undefined so we can set the timeline to null
  4481. if (typeof timeline === 'undefined') return this._timeline;
  4482. this._timeline = timeline;
  4483. return this;
  4484. }
  4485. unschedule() {
  4486. const timeline = this.timeline();
  4487. timeline && timeline.unschedule(this);
  4488. return this;
  4489. } // Run each initialise function in the runner if required
  4490. _initialise(running) {
  4491. // If we aren't running, we shouldn't initialise when not declarative
  4492. if (!running && !this._isDeclarative) return; // Loop through all of the initialisers
  4493. for (let i = 0, len = this._queue.length; i < len; ++i) {
  4494. // Get the current initialiser
  4495. const current = this._queue[i]; // Determine whether we need to initialise
  4496. const needsIt = this._isDeclarative || !current.initialised && running;
  4497. running = !current.finished; // Call the initialiser if we need to
  4498. if (needsIt && running) {
  4499. current.initialiser.call(this);
  4500. current.initialised = true;
  4501. }
  4502. }
  4503. } // Save a morpher to the morpher list so that we can retarget it later
  4504. _rememberMorpher(method, morpher) {
  4505. this._history[method] = {
  4506. morpher: morpher,
  4507. caller: this._queue[this._queue.length - 1]
  4508. }; // We have to resume the timeline in case a controller
  4509. // is already done without being ever run
  4510. // This can happen when e.g. this is done:
  4511. // anim = el.animate(new SVG.Spring)
  4512. // and later
  4513. // anim.move(...)
  4514. if (this._isDeclarative) {
  4515. const timeline = this.timeline();
  4516. timeline && timeline.play();
  4517. }
  4518. } // Try to set the target for a morpher if the morpher exists, otherwise
  4519. // Run each run function for the position or dt given
  4520. _run(positionOrDt) {
  4521. // Run all of the _queue directly
  4522. let allfinished = true;
  4523. for (let i = 0, len = this._queue.length; i < len; ++i) {
  4524. // Get the current function to run
  4525. const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
  4526. // flag for the sake of declarative _queue
  4527. const converged = current.runner.call(this, positionOrDt);
  4528. current.finished = current.finished || converged === true;
  4529. allfinished = allfinished && current.finished;
  4530. } // We report when all of the constructors are finished
  4531. return allfinished;
  4532. } // do nothing and return false
  4533. _tryRetarget(method, target, extra) {
  4534. if (this._history[method]) {
  4535. // if the last method wasn't even initialised, throw it away
  4536. if (!this._history[method].caller.initialised) {
  4537. const index = this._queue.indexOf(this._history[method].caller);
  4538. this._queue.splice(index, 1);
  4539. return false;
  4540. } // for the case of transformations, we use the special retarget function
  4541. // which has access to the outer scope
  4542. if (this._history[method].caller.retarget) {
  4543. this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient
  4544. } else {
  4545. this._history[method].morpher.to(target);
  4546. }
  4547. this._history[method].caller.finished = false;
  4548. const timeline = this.timeline();
  4549. timeline && timeline.play();
  4550. return true;
  4551. }
  4552. return false;
  4553. }
  4554. }
  4555. Runner.id = 0;
  4556. class FakeRunner {
  4557. constructor(transforms = new Matrix(), id = -1, done = true) {
  4558. this.transforms = transforms;
  4559. this.id = id;
  4560. this.done = done;
  4561. }
  4562. clearTransformsFromQueue() {}
  4563. }
  4564. extend([Runner, FakeRunner], {
  4565. mergeWith(runner) {
  4566. return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
  4567. }
  4568. }); // FakeRunner.emptyRunner = new FakeRunner()
  4569. const lmultiply = (last, curr) => last.lmultiplyO(curr);
  4570. const getRunnerTransform = runner => runner.transforms;
  4571. function mergeTransforms() {
  4572. // Find the matrix to apply to the element and apply it
  4573. const runners = this._transformationRunners.runners;
  4574. const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
  4575. this.transform(netTransform);
  4576. this._transformationRunners.merge();
  4577. if (this._transformationRunners.length() === 1) {
  4578. this._frameId = null;
  4579. }
  4580. }
  4581. class RunnerArray {
  4582. constructor() {
  4583. this.runners = [];
  4584. this.ids = [];
  4585. }
  4586. add(runner) {
  4587. if (this.runners.includes(runner)) return;
  4588. const id = runner.id + 1;
  4589. this.runners.push(runner);
  4590. this.ids.push(id);
  4591. return this;
  4592. }
  4593. clearBefore(id) {
  4594. const deleteCnt = this.ids.indexOf(id + 1) || 1;
  4595. this.ids.splice(0, deleteCnt, 0);
  4596. this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue());
  4597. return this;
  4598. }
  4599. edit(id, newRunner) {
  4600. const index = this.ids.indexOf(id + 1);
  4601. this.ids.splice(index, 1, id + 1);
  4602. this.runners.splice(index, 1, newRunner);
  4603. return this;
  4604. }
  4605. getByID(id) {
  4606. return this.runners[this.ids.indexOf(id + 1)];
  4607. }
  4608. length() {
  4609. return this.ids.length;
  4610. }
  4611. merge() {
  4612. let lastRunner = null;
  4613. for (let i = 0; i < this.runners.length; ++i) {
  4614. const runner = this.runners[i];
  4615. const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline
  4616. && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id));
  4617. if (condition) {
  4618. // the +1 happens in the function
  4619. this.remove(runner.id);
  4620. const newRunner = runner.mergeWith(lastRunner);
  4621. this.edit(lastRunner.id, newRunner);
  4622. lastRunner = newRunner;
  4623. --i;
  4624. } else {
  4625. lastRunner = runner;
  4626. }
  4627. }
  4628. return this;
  4629. }
  4630. remove(id) {
  4631. const index = this.ids.indexOf(id + 1);
  4632. this.ids.splice(index, 1);
  4633. this.runners.splice(index, 1);
  4634. return this;
  4635. }
  4636. }
  4637. registerMethods({
  4638. Element: {
  4639. animate(duration, delay, when) {
  4640. const o = Runner.sanitise(duration, delay, when);
  4641. const timeline = this.timeline();
  4642. return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when);
  4643. },
  4644. delay(by, when) {
  4645. return this.animate(0, by, when);
  4646. },
  4647. // this function searches for all runners on the element and deletes the ones
  4648. // which run before the current one. This is because absolute transformations
  4649. // overwrite anything anyway so there is no need to waste time computing
  4650. // other runners
  4651. _clearTransformRunnersBefore(currentRunner) {
  4652. this._transformationRunners.clearBefore(currentRunner.id);
  4653. },
  4654. _currentTransform(current) {
  4655. return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations
  4656. // on the same runner which execute before the current transformation are
  4657. // taken into account
  4658. .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix());
  4659. },
  4660. _addRunner(runner) {
  4661. this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of
  4662. // all Animator functions. That is why we use immediate here to execute
  4663. // the merge right after all frames are run
  4664. Animator.cancelImmediate(this._frameId);
  4665. this._frameId = Animator.immediate(mergeTransforms.bind(this));
  4666. },
  4667. _prepareRunner() {
  4668. if (this._frameId == null) {
  4669. this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
  4670. }
  4671. }
  4672. }
  4673. }); // Will output the elements from array A that are not in the array B
  4674. const difference = (a, b) => a.filter(x => !b.includes(x));
  4675. extend(Runner, {
  4676. attr(a, v) {
  4677. return this.styleAttr('attr', a, v);
  4678. },
  4679. // Add animatable styles
  4680. css(s, v) {
  4681. return this.styleAttr('css', s, v);
  4682. },
  4683. styleAttr(type, nameOrAttrs, val) {
  4684. if (typeof nameOrAttrs === 'string') {
  4685. return this.styleAttr(type, {
  4686. [nameOrAttrs]: val
  4687. });
  4688. }
  4689. let attrs = nameOrAttrs;
  4690. if (this._tryRetarget(type, attrs)) return this;
  4691. let morpher = new Morphable(this._stepper).to(attrs);
  4692. let keys = Object.keys(attrs);
  4693. this.queue(function () {
  4694. morpher = morpher.from(this.element()[type](keys));
  4695. }, function (pos) {
  4696. this.element()[type](morpher.at(pos).valueOf());
  4697. return morpher.done();
  4698. }, function (newToAttrs) {
  4699. // Check if any new keys were added
  4700. const newKeys = Object.keys(newToAttrs);
  4701. const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher
  4702. if (differences.length) {
  4703. // Get the values
  4704. const addedFromAttrs = this.element()[type](differences); // Get the already initialized values
  4705. const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new
  4706. Object.assign(oldFromAttrs, addedFromAttrs);
  4707. morpher.from(oldFromAttrs);
  4708. } // Get the object from the morpher
  4709. const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes
  4710. Object.assign(oldToAttrs, newToAttrs); // Change morpher target
  4711. morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again
  4712. keys = newKeys;
  4713. attrs = newToAttrs;
  4714. });
  4715. this._rememberMorpher(type, morpher);
  4716. return this;
  4717. },
  4718. zoom(level, point) {
  4719. if (this._tryRetarget('zoom', level, point)) return this;
  4720. let morpher = new Morphable(this._stepper).to(new SVGNumber(level));
  4721. this.queue(function () {
  4722. morpher = morpher.from(this.element().zoom());
  4723. }, function (pos) {
  4724. this.element().zoom(morpher.at(pos), point);
  4725. return morpher.done();
  4726. }, function (newLevel, newPoint) {
  4727. point = newPoint;
  4728. morpher.to(newLevel);
  4729. });
  4730. this._rememberMorpher('zoom', morpher);
  4731. return this;
  4732. },
  4733. /**
  4734. ** absolute transformations
  4735. **/
  4736. //
  4737. // M v -----|-----(D M v = F v)------|-----> T v
  4738. //
  4739. // 1. define the final state (T) and decompose it (once)
  4740. // t = [tx, ty, the, lam, sy, sx]
  4741. // 2. on every frame: pull the current state of all previous transforms
  4742. // (M - m can change)
  4743. // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
  4744. // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
  4745. // - Note F(0) = M
  4746. // - Note F(1) = T
  4747. // 4. Now you get the delta matrix as a result: D = F * inv(M)
  4748. transform(transforms, relative, affine) {
  4749. // If we have a declarative function, we should retarget it if possible
  4750. relative = transforms.relative || relative;
  4751. if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
  4752. return this;
  4753. } // Parse the parameters
  4754. const isMatrix = Matrix.isMatrixLike(transforms);
  4755. affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morpher and set its type
  4756. const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
  4757. let origin;
  4758. let element;
  4759. let current;
  4760. let currentAngle;
  4761. let startTransform;
  4762. function setup() {
  4763. // make sure element and origin is defined
  4764. element = element || this.element();
  4765. origin = origin || getOrigin(transforms, element);
  4766. startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations
  4767. element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute
  4768. if (!relative) {
  4769. element._clearTransformRunnersBefore(this);
  4770. }
  4771. }
  4772. function run(pos) {
  4773. // clear all other transforms before this in case something is saved
  4774. // on this runner. We are absolute. We dont need these!
  4775. if (!relative) this.clearTransform();
  4776. const {
  4777. x,
  4778. y
  4779. } = new Point(origin).transform(element._currentTransform(this));
  4780. let target = new Matrix({ ...transforms,
  4781. origin: [x, y]
  4782. });
  4783. let start = this._isDeclarative && current ? current : startTransform;
  4784. if (affine) {
  4785. target = target.decompose(x, y);
  4786. start = start.decompose(x, y); // Get the current and target angle as it was set
  4787. const rTarget = target.rotate;
  4788. const rCurrent = start.rotate; // Figure out the shortest path to rotate directly
  4789. const possibilities = [rTarget - 360, rTarget, rTarget + 360];
  4790. const distances = possibilities.map(a => Math.abs(a - rCurrent));
  4791. const shortest = Math.min(...distances);
  4792. const index = distances.indexOf(shortest);
  4793. target.rotate = possibilities[index];
  4794. }
  4795. if (relative) {
  4796. // we have to be careful here not to overwrite the rotation
  4797. // with the rotate method of Matrix
  4798. if (!isMatrix) {
  4799. target.rotate = transforms.rotate || 0;
  4800. }
  4801. if (this._isDeclarative && currentAngle) {
  4802. start.rotate = currentAngle;
  4803. }
  4804. }
  4805. morpher.from(start);
  4806. morpher.to(target);
  4807. const affineParameters = morpher.at(pos);
  4808. currentAngle = affineParameters.rotate;
  4809. current = new Matrix(affineParameters);
  4810. this.addTransform(current);
  4811. element._addRunner(this);
  4812. return morpher.done();
  4813. }
  4814. function retarget(newTransforms) {
  4815. // only get a new origin if it changed since the last call
  4816. if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) {
  4817. origin = getOrigin(newTransforms, element);
  4818. } // overwrite the old transformations with the new ones
  4819. transforms = { ...newTransforms,
  4820. origin
  4821. };
  4822. }
  4823. this.queue(setup, run, retarget, true);
  4824. this._isDeclarative && this._rememberMorpher('transform', morpher);
  4825. return this;
  4826. },
  4827. // Animatable x-axis
  4828. x(x, relative) {
  4829. return this._queueNumber('x', x);
  4830. },
  4831. // Animatable y-axis
  4832. y(y) {
  4833. return this._queueNumber('y', y);
  4834. },
  4835. dx(x = 0) {
  4836. return this._queueNumberDelta('x', x);
  4837. },
  4838. dy(y = 0) {
  4839. return this._queueNumberDelta('y', y);
  4840. },
  4841. dmove(x, y) {
  4842. return this.dx(x).dy(y);
  4843. },
  4844. _queueNumberDelta(method, to) {
  4845. to = new SVGNumber(to); // Try to change the target if we have this method already registered
  4846. if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation
  4847. const morpher = new Morphable(this._stepper).to(to);
  4848. let from = null;
  4849. this.queue(function () {
  4850. from = this.element()[method]();
  4851. morpher.from(from);
  4852. morpher.to(from + to);
  4853. }, function (pos) {
  4854. this.element()[method](morpher.at(pos));
  4855. return morpher.done();
  4856. }, function (newTo) {
  4857. morpher.to(from + new SVGNumber(newTo));
  4858. }); // Register the morpher so that if it is changed again, we can retarget it
  4859. this._rememberMorpher(method, morpher);
  4860. return this;
  4861. },
  4862. _queueObject(method, to) {
  4863. // Try to change the target if we have this method already registered
  4864. if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation
  4865. const morpher = new Morphable(this._stepper).to(to);
  4866. this.queue(function () {
  4867. morpher.from(this.element()[method]());
  4868. }, function (pos) {
  4869. this.element()[method](morpher.at(pos));
  4870. return morpher.done();
  4871. }); // Register the morpher so that if it is changed again, we can retarget it
  4872. this._rememberMorpher(method, morpher);
  4873. return this;
  4874. },
  4875. _queueNumber(method, value) {
  4876. return this._queueObject(method, new SVGNumber(value));
  4877. },
  4878. // Animatable center x-axis
  4879. cx(x) {
  4880. return this._queueNumber('cx', x);
  4881. },
  4882. // Animatable center y-axis
  4883. cy(y) {
  4884. return this._queueNumber('cy', y);
  4885. },
  4886. // Add animatable move
  4887. move(x, y) {
  4888. return this.x(x).y(y);
  4889. },
  4890. // Add animatable center
  4891. center(x, y) {
  4892. return this.cx(x).cy(y);
  4893. },
  4894. // Add animatable size
  4895. size(width, height) {
  4896. // animate bbox based size for all other elements
  4897. let box;
  4898. if (!width || !height) {
  4899. box = this._element.bbox();
  4900. }
  4901. if (!width) {
  4902. width = box.width / box.height * height;
  4903. }
  4904. if (!height) {
  4905. height = box.height / box.width * width;
  4906. }
  4907. return this.width(width).height(height);
  4908. },
  4909. // Add animatable width
  4910. width(width) {
  4911. return this._queueNumber('width', width);
  4912. },
  4913. // Add animatable height
  4914. height(height) {
  4915. return this._queueNumber('height', height);
  4916. },
  4917. // Add animatable plot
  4918. plot(a, b, c, d) {
  4919. // Lines can be plotted with 4 arguments
  4920. if (arguments.length === 4) {
  4921. return this.plot([a, b, c, d]);
  4922. }
  4923. if (this._tryRetarget('plot', a)) return this;
  4924. const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a);
  4925. this.queue(function () {
  4926. morpher.from(this._element.array());
  4927. }, function (pos) {
  4928. this._element.plot(morpher.at(pos));
  4929. return morpher.done();
  4930. });
  4931. this._rememberMorpher('plot', morpher);
  4932. return this;
  4933. },
  4934. // Add leading method
  4935. leading(value) {
  4936. return this._queueNumber('leading', value);
  4937. },
  4938. // Add animatable viewbox
  4939. viewbox(x, y, width, height) {
  4940. return this._queueObject('viewbox', new Box(x, y, width, height));
  4941. },
  4942. update(o) {
  4943. if (typeof o !== 'object') {
  4944. return this.update({
  4945. offset: arguments[0],
  4946. color: arguments[1],
  4947. opacity: arguments[2]
  4948. });
  4949. }
  4950. if (o.opacity != null) this.attr('stop-opacity', o.opacity);
  4951. if (o.color != null) this.attr('stop-color', o.color);
  4952. if (o.offset != null) this.attr('offset', o.offset);
  4953. return this;
  4954. }
  4955. });
  4956. extend(Runner, {
  4957. rx,
  4958. ry,
  4959. from,
  4960. to
  4961. });
  4962. register(Runner, 'Runner');
  4963. class Svg extends Container {
  4964. constructor(node, attrs = node) {
  4965. super(nodeOrNew('svg', node), attrs);
  4966. this.namespace();
  4967. } // Creates and returns defs element
  4968. defs() {
  4969. if (!this.isRoot()) return this.root().defs();
  4970. return adopt(this.node.querySelector('defs')) || this.put(new Defs());
  4971. }
  4972. isRoot() {
  4973. return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment';
  4974. } // Add namespaces
  4975. namespace() {
  4976. if (!this.isRoot()) return this.root().namespace();
  4977. return this.attr({
  4978. xmlns: svg,
  4979. version: '1.1'
  4980. }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns);
  4981. }
  4982. removeNamespace() {
  4983. return this.attr({
  4984. xmlns: null,
  4985. version: null
  4986. }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns);
  4987. } // Check if this is a root svg
  4988. // If not, call root() from this element
  4989. root() {
  4990. if (this.isRoot()) return this;
  4991. return super.root();
  4992. }
  4993. }
  4994. registerMethods({
  4995. Container: {
  4996. // Create nested svg document
  4997. nested: wrapWithAttrCheck(function () {
  4998. return this.put(new Svg());
  4999. })
  5000. }
  5001. });
  5002. register(Svg, 'Svg', true);
  5003. class Symbol extends Container {
  5004. // Initialize node
  5005. constructor(node, attrs = node) {
  5006. super(nodeOrNew('symbol', node), attrs);
  5007. }
  5008. }
  5009. registerMethods({
  5010. Container: {
  5011. symbol: wrapWithAttrCheck(function () {
  5012. return this.put(new Symbol());
  5013. })
  5014. }
  5015. });
  5016. register(Symbol, 'Symbol');
  5017. function plain(text) {
  5018. // clear if build mode is disabled
  5019. if (this._build === false) {
  5020. this.clear();
  5021. } // create text node
  5022. this.node.appendChild(globals.document.createTextNode(text));
  5023. return this;
  5024. } // Get length of text element
  5025. function length() {
  5026. return this.node.getComputedTextLength();
  5027. } // Move over x-axis
  5028. // Text is moved by its bounding box
  5029. // text-anchor does NOT matter
  5030. function x$1(x, box = this.bbox()) {
  5031. if (x == null) {
  5032. return box.x;
  5033. }
  5034. return this.attr('x', this.attr('x') + x - box.x);
  5035. } // Move over y-axis
  5036. function y$1(y, box = this.bbox()) {
  5037. if (y == null) {
  5038. return box.y;
  5039. }
  5040. return this.attr('y', this.attr('y') + y - box.y);
  5041. }
  5042. function move$1(x, y, box = this.bbox()) {
  5043. return this.x(x, box).y(y, box);
  5044. } // Move center over x-axis
  5045. function cx(x, box = this.bbox()) {
  5046. if (x == null) {
  5047. return box.cx;
  5048. }
  5049. return this.attr('x', this.attr('x') + x - box.cx);
  5050. } // Move center over y-axis
  5051. function cy(y, box = this.bbox()) {
  5052. if (y == null) {
  5053. return box.cy;
  5054. }
  5055. return this.attr('y', this.attr('y') + y - box.cy);
  5056. }
  5057. function center(x, y, box = this.bbox()) {
  5058. return this.cx(x, box).cy(y, box);
  5059. }
  5060. function ax(x) {
  5061. return this.attr('x', x);
  5062. }
  5063. function ay(y) {
  5064. return this.attr('y', y);
  5065. }
  5066. function amove(x, y) {
  5067. return this.ax(x).ay(y);
  5068. } // Enable / disable build mode
  5069. function build(build) {
  5070. this._build = !!build;
  5071. return this;
  5072. }
  5073. var textable = {
  5074. __proto__: null,
  5075. plain: plain,
  5076. length: length,
  5077. x: x$1,
  5078. y: y$1,
  5079. move: move$1,
  5080. cx: cx,
  5081. cy: cy,
  5082. center: center,
  5083. ax: ax,
  5084. ay: ay,
  5085. amove: amove,
  5086. build: build
  5087. };
  5088. class Text extends Shape {
  5089. // Initialize node
  5090. constructor(node, attrs = node) {
  5091. super(nodeOrNew('text', node), attrs);
  5092. this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding
  5093. this._rebuild = true; // enable automatic updating of dy values
  5094. this._build = false; // disable build mode for adding multiple lines
  5095. } // Set / get leading
  5096. leading(value) {
  5097. // act as getter
  5098. if (value == null) {
  5099. return this.dom.leading;
  5100. } // act as setter
  5101. this.dom.leading = new SVGNumber(value);
  5102. return this.rebuild();
  5103. } // Rebuild appearance type
  5104. rebuild(rebuild) {
  5105. // store new rebuild flag if given
  5106. if (typeof rebuild === 'boolean') {
  5107. this._rebuild = rebuild;
  5108. } // define position of all lines
  5109. if (this._rebuild) {
  5110. const self = this;
  5111. let blankLineOffset = 0;
  5112. const leading = this.dom.leading;
  5113. this.each(function (i) {
  5114. const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
  5115. const dy = leading * new SVGNumber(fontSize);
  5116. if (this.dom.newLined) {
  5117. this.attr('x', self.attr('x'));
  5118. if (this.text() === '\n') {
  5119. blankLineOffset += dy;
  5120. } else {
  5121. this.attr('dy', i ? dy + blankLineOffset : 0);
  5122. blankLineOffset = 0;
  5123. }
  5124. }
  5125. });
  5126. this.fire('rebuild');
  5127. }
  5128. return this;
  5129. } // overwrite method from parent to set data properly
  5130. setData(o) {
  5131. this.dom = o;
  5132. this.dom.leading = new SVGNumber(o.leading || 1.3);
  5133. return this;
  5134. } // Set the text content
  5135. text(text) {
  5136. // act as getter
  5137. if (text === undefined) {
  5138. const children = this.node.childNodes;
  5139. let firstLine = 0;
  5140. text = '';
  5141. for (let i = 0, len = children.length; i < len; ++i) {
  5142. // skip textPaths - they are no lines
  5143. if (children[i].nodeName === 'textPath') {
  5144. if (i === 0) firstLine = 1;
  5145. continue;
  5146. } // add newline if its not the first child and newLined is set to true
  5147. if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
  5148. text += '\n';
  5149. } // add content of this node
  5150. text += children[i].textContent;
  5151. }
  5152. return text;
  5153. } // remove existing content
  5154. this.clear().build(true);
  5155. if (typeof text === 'function') {
  5156. // call block
  5157. text.call(this, this);
  5158. } else {
  5159. // store text and make sure text is not blank
  5160. text = (text + '').split('\n'); // build new lines
  5161. for (let j = 0, jl = text.length; j < jl; j++) {
  5162. this.newLine(text[j]);
  5163. }
  5164. } // disable build mode and rebuild lines
  5165. return this.build(false).rebuild();
  5166. }
  5167. }
  5168. extend(Text, textable);
  5169. registerMethods({
  5170. Container: {
  5171. // Create text element
  5172. text: wrapWithAttrCheck(function (text = '') {
  5173. return this.put(new Text()).text(text);
  5174. }),
  5175. // Create plain text element
  5176. plain: wrapWithAttrCheck(function (text = '') {
  5177. return this.put(new Text()).plain(text);
  5178. })
  5179. }
  5180. });
  5181. register(Text, 'Text');
  5182. class Tspan extends Shape {
  5183. // Initialize node
  5184. constructor(node, attrs = node) {
  5185. super(nodeOrNew('tspan', node), attrs);
  5186. this._build = false; // disable build mode for adding multiple lines
  5187. } // Shortcut dx
  5188. dx(dx) {
  5189. return this.attr('dx', dx);
  5190. } // Shortcut dy
  5191. dy(dy) {
  5192. return this.attr('dy', dy);
  5193. } // Create new line
  5194. newLine() {
  5195. // mark new line
  5196. this.dom.newLined = true; // fetch parent
  5197. const text = this.parent(); // early return in case we are not in a text element
  5198. if (!(text instanceof Text)) {
  5199. return this;
  5200. }
  5201. const i = text.index(this);
  5202. const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
  5203. const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position
  5204. return this.dy(i ? dy : 0).attr('x', text.x());
  5205. } // Set text content
  5206. text(text) {
  5207. if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '');
  5208. if (typeof text === 'function') {
  5209. this.clear().build(true);
  5210. text.call(this, this);
  5211. this.build(false);
  5212. } else {
  5213. this.plain(text);
  5214. }
  5215. return this;
  5216. }
  5217. }
  5218. extend(Tspan, textable);
  5219. registerMethods({
  5220. Tspan: {
  5221. tspan: wrapWithAttrCheck(function (text = '') {
  5222. const tspan = new Tspan(); // clear if build mode is disabled
  5223. if (!this._build) {
  5224. this.clear();
  5225. } // add new tspan
  5226. return this.put(tspan).text(text);
  5227. })
  5228. },
  5229. Text: {
  5230. newLine: function (text = '') {
  5231. return this.tspan(text).newLine();
  5232. }
  5233. }
  5234. });
  5235. register(Tspan, 'Tspan');
  5236. class Circle extends Shape {
  5237. constructor(node, attrs = node) {
  5238. super(nodeOrNew('circle', node), attrs);
  5239. }
  5240. radius(r) {
  5241. return this.attr('r', r);
  5242. } // Radius x value
  5243. rx(rx) {
  5244. return this.attr('r', rx);
  5245. } // Alias radius x value
  5246. ry(ry) {
  5247. return this.rx(ry);
  5248. }
  5249. size(size) {
  5250. return this.radius(new SVGNumber(size).divide(2));
  5251. }
  5252. }
  5253. extend(Circle, {
  5254. x: x$3,
  5255. y: y$3,
  5256. cx: cx$1,
  5257. cy: cy$1,
  5258. width: width$2,
  5259. height: height$2
  5260. });
  5261. registerMethods({
  5262. Container: {
  5263. // Create circle element
  5264. circle: wrapWithAttrCheck(function (size = 0) {
  5265. return this.put(new Circle()).size(size).move(0, 0);
  5266. })
  5267. }
  5268. });
  5269. register(Circle, 'Circle');
  5270. class ClipPath extends Container {
  5271. constructor(node, attrs = node) {
  5272. super(nodeOrNew('clipPath', node), attrs);
  5273. } // Unclip all clipped elements and remove itself
  5274. remove() {
  5275. // unclip all targets
  5276. this.targets().forEach(function (el) {
  5277. el.unclip();
  5278. }); // remove clipPath from parent
  5279. return super.remove();
  5280. }
  5281. targets() {
  5282. return baseFind('svg [clip-path*=' + this.id() + ']');
  5283. }
  5284. }
  5285. registerMethods({
  5286. Container: {
  5287. // Create clipping element
  5288. clip: wrapWithAttrCheck(function () {
  5289. return this.defs().put(new ClipPath());
  5290. })
  5291. },
  5292. Element: {
  5293. // Distribute clipPath to svg element
  5294. clipper() {
  5295. return this.reference('clip-path');
  5296. },
  5297. clipWith(element) {
  5298. // use given clip or create a new one
  5299. const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask
  5300. return this.attr('clip-path', 'url(#' + clipper.id() + ')');
  5301. },
  5302. // Unclip element
  5303. unclip() {
  5304. return this.attr('clip-path', null);
  5305. }
  5306. }
  5307. });
  5308. register(ClipPath, 'ClipPath');
  5309. class ForeignObject extends Element {
  5310. constructor(node, attrs = node) {
  5311. super(nodeOrNew('foreignObject', node), attrs);
  5312. }
  5313. }
  5314. registerMethods({
  5315. Container: {
  5316. foreignObject: wrapWithAttrCheck(function (width, height) {
  5317. return this.put(new ForeignObject()).size(width, height);
  5318. })
  5319. }
  5320. });
  5321. register(ForeignObject, 'ForeignObject');
  5322. function dmove(dx, dy) {
  5323. this.children().forEach((child, i) => {
  5324. let bbox; // We have to wrap this for elements that dont have a bbox
  5325. // e.g. title and other descriptive elements
  5326. try {
  5327. // Get the childs bbox
  5328. bbox = child.bbox();
  5329. } catch (e) {
  5330. return;
  5331. } // Get childs matrix
  5332. const m = new Matrix(child); // Translate childs matrix by amount and
  5333. // transform it back into parents space
  5334. const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box
  5335. const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element
  5336. child.move(p.x, p.y);
  5337. });
  5338. return this;
  5339. }
  5340. function dx(dx) {
  5341. return this.dmove(dx, 0);
  5342. }
  5343. function dy(dy) {
  5344. return this.dmove(0, dy);
  5345. }
  5346. function height(height, box = this.bbox()) {
  5347. if (height == null) return box.height;
  5348. return this.size(box.width, height, box);
  5349. }
  5350. function move(x = 0, y = 0, box = this.bbox()) {
  5351. const dx = x - box.x;
  5352. const dy = y - box.y;
  5353. return this.dmove(dx, dy);
  5354. }
  5355. function size(width, height, box = this.bbox()) {
  5356. const p = proportionalSize(this, width, height, box);
  5357. const scaleX = p.width / box.width;
  5358. const scaleY = p.height / box.height;
  5359. this.children().forEach((child, i) => {
  5360. const o = new Point(box).transform(new Matrix(child).inverse());
  5361. child.scale(scaleX, scaleY, o.x, o.y);
  5362. });
  5363. return this;
  5364. }
  5365. function width(width, box = this.bbox()) {
  5366. if (width == null) return box.width;
  5367. return this.size(width, box.height, box);
  5368. }
  5369. function x(x, box = this.bbox()) {
  5370. if (x == null) return box.x;
  5371. return this.move(x, box.y, box);
  5372. }
  5373. function y(y, box = this.bbox()) {
  5374. if (y == null) return box.y;
  5375. return this.move(box.x, y, box);
  5376. }
  5377. var containerGeometry = {
  5378. __proto__: null,
  5379. dmove: dmove,
  5380. dx: dx,
  5381. dy: dy,
  5382. height: height,
  5383. move: move,
  5384. size: size,
  5385. width: width,
  5386. x: x,
  5387. y: y
  5388. };
  5389. class G extends Container {
  5390. constructor(node, attrs = node) {
  5391. super(nodeOrNew('g', node), attrs);
  5392. }
  5393. }
  5394. extend(G, containerGeometry);
  5395. registerMethods({
  5396. Container: {
  5397. // Create a group element
  5398. group: wrapWithAttrCheck(function () {
  5399. return this.put(new G());
  5400. })
  5401. }
  5402. });
  5403. register(G, 'G');
  5404. class A extends Container {
  5405. constructor(node, attrs = node) {
  5406. super(nodeOrNew('a', node), attrs);
  5407. } // Link target attribute
  5408. target(target) {
  5409. return this.attr('target', target);
  5410. } // Link url
  5411. to(url) {
  5412. return this.attr('href', url, xlink);
  5413. }
  5414. }
  5415. extend(A, containerGeometry);
  5416. registerMethods({
  5417. Container: {
  5418. // Create a hyperlink element
  5419. link: wrapWithAttrCheck(function (url) {
  5420. return this.put(new A()).to(url);
  5421. })
  5422. },
  5423. Element: {
  5424. unlink() {
  5425. const link = this.linker();
  5426. if (!link) return this;
  5427. const parent = link.parent();
  5428. if (!parent) {
  5429. return this.remove();
  5430. }
  5431. const index = parent.index(link);
  5432. parent.add(this, index);
  5433. link.remove();
  5434. return this;
  5435. },
  5436. linkTo(url) {
  5437. // reuse old link if possible
  5438. let link = this.linker();
  5439. if (!link) {
  5440. link = new A();
  5441. this.wrap(link);
  5442. }
  5443. if (typeof url === 'function') {
  5444. url.call(link, link);
  5445. } else {
  5446. link.to(url);
  5447. }
  5448. return this;
  5449. },
  5450. linker() {
  5451. const link = this.parent();
  5452. if (link && link.node.nodeName.toLowerCase() === 'a') {
  5453. return link;
  5454. }
  5455. return null;
  5456. }
  5457. }
  5458. });
  5459. register(A, 'A');
  5460. class Mask extends Container {
  5461. // Initialize node
  5462. constructor(node, attrs = node) {
  5463. super(nodeOrNew('mask', node), attrs);
  5464. } // Unmask all masked elements and remove itself
  5465. remove() {
  5466. // unmask all targets
  5467. this.targets().forEach(function (el) {
  5468. el.unmask();
  5469. }); // remove mask from parent
  5470. return super.remove();
  5471. }
  5472. targets() {
  5473. return baseFind('svg [mask*=' + this.id() + ']');
  5474. }
  5475. }
  5476. registerMethods({
  5477. Container: {
  5478. mask: wrapWithAttrCheck(function () {
  5479. return this.defs().put(new Mask());
  5480. })
  5481. },
  5482. Element: {
  5483. // Distribute mask to svg element
  5484. masker() {
  5485. return this.reference('mask');
  5486. },
  5487. maskWith(element) {
  5488. // use given mask or create a new one
  5489. const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask
  5490. return this.attr('mask', 'url(#' + masker.id() + ')');
  5491. },
  5492. // Unmask element
  5493. unmask() {
  5494. return this.attr('mask', null);
  5495. }
  5496. }
  5497. });
  5498. register(Mask, 'Mask');
  5499. class Stop extends Element {
  5500. constructor(node, attrs = node) {
  5501. super(nodeOrNew('stop', node), attrs);
  5502. } // add color stops
  5503. update(o) {
  5504. if (typeof o === 'number' || o instanceof SVGNumber) {
  5505. o = {
  5506. offset: arguments[0],
  5507. color: arguments[1],
  5508. opacity: arguments[2]
  5509. };
  5510. } // set attributes
  5511. if (o.opacity != null) this.attr('stop-opacity', o.opacity);
  5512. if (o.color != null) this.attr('stop-color', o.color);
  5513. if (o.offset != null) this.attr('offset', new SVGNumber(o.offset));
  5514. return this;
  5515. }
  5516. }
  5517. registerMethods({
  5518. Gradient: {
  5519. // Add a color stop
  5520. stop: function (offset, color, opacity) {
  5521. return this.put(new Stop()).update(offset, color, opacity);
  5522. }
  5523. }
  5524. });
  5525. register(Stop, 'Stop');
  5526. function cssRule(selector, rule) {
  5527. if (!selector) return '';
  5528. if (!rule) return selector;
  5529. let ret = selector + '{';
  5530. for (const i in rule) {
  5531. ret += unCamelCase(i) + ':' + rule[i] + ';';
  5532. }
  5533. ret += '}';
  5534. return ret;
  5535. }
  5536. class Style extends Element {
  5537. constructor(node, attrs = node) {
  5538. super(nodeOrNew('style', node), attrs);
  5539. }
  5540. addText(w = '') {
  5541. this.node.textContent += w;
  5542. return this;
  5543. }
  5544. font(name, src, params = {}) {
  5545. return this.rule('@font-face', {
  5546. fontFamily: name,
  5547. src: src,
  5548. ...params
  5549. });
  5550. }
  5551. rule(selector, obj) {
  5552. return this.addText(cssRule(selector, obj));
  5553. }
  5554. }
  5555. registerMethods('Dom', {
  5556. style(selector, obj) {
  5557. return this.put(new Style()).rule(selector, obj);
  5558. },
  5559. fontface(name, src, params) {
  5560. return this.put(new Style()).font(name, src, params);
  5561. }
  5562. });
  5563. register(Style, 'Style');
  5564. class TextPath extends Text {
  5565. // Initialize node
  5566. constructor(node, attrs = node) {
  5567. super(nodeOrNew('textPath', node), attrs);
  5568. } // return the array of the path track element
  5569. array() {
  5570. const track = this.track();
  5571. return track ? track.array() : null;
  5572. } // Plot path if any
  5573. plot(d) {
  5574. const track = this.track();
  5575. let pathArray = null;
  5576. if (track) {
  5577. pathArray = track.plot(d);
  5578. }
  5579. return d == null ? pathArray : this;
  5580. } // Get the path element
  5581. track() {
  5582. return this.reference('href');
  5583. }
  5584. }
  5585. registerMethods({
  5586. Container: {
  5587. textPath: wrapWithAttrCheck(function (text, path) {
  5588. // Convert text to instance if needed
  5589. if (!(text instanceof Text)) {
  5590. text = this.text(text);
  5591. }
  5592. return text.path(path);
  5593. })
  5594. },
  5595. Text: {
  5596. // Create path for text to run on
  5597. path: wrapWithAttrCheck(function (track, importNodes = true) {
  5598. const textPath = new TextPath(); // if track is a path, reuse it
  5599. if (!(track instanceof Path)) {
  5600. // create path element
  5601. track = this.defs().path(track);
  5602. } // link textPath to path and add content
  5603. textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath
  5604. let node;
  5605. if (importNodes) {
  5606. while (node = this.node.firstChild) {
  5607. textPath.node.appendChild(node);
  5608. }
  5609. } // add textPath element as child node and return textPath
  5610. return this.put(textPath);
  5611. }),
  5612. // Get the textPath children
  5613. textPath() {
  5614. return this.findOne('textPath');
  5615. }
  5616. },
  5617. Path: {
  5618. // creates a textPath from this path
  5619. text: wrapWithAttrCheck(function (text) {
  5620. // Convert text to instance if needed
  5621. if (!(text instanceof Text)) {
  5622. text = new Text().addTo(this.parent()).text(text);
  5623. } // Create textPath from text and path and return
  5624. return text.path(this);
  5625. }),
  5626. targets() {
  5627. return baseFind('svg textPath').filter(node => {
  5628. return (node.attr('href') || '').includes(this.id());
  5629. }); // Does not work in IE11. Use when IE support is dropped
  5630. // return baseFind('svg textPath[*|href*=' + this.id() + ']')
  5631. }
  5632. }
  5633. });
  5634. TextPath.prototype.MorphArray = PathArray;
  5635. register(TextPath, 'TextPath');
  5636. class Use extends Shape {
  5637. constructor(node, attrs = node) {
  5638. super(nodeOrNew('use', node), attrs);
  5639. } // Use element as a reference
  5640. use(element, file) {
  5641. // Set lined element
  5642. return this.attr('href', (file || '') + '#' + element, xlink);
  5643. }
  5644. }
  5645. registerMethods({
  5646. Container: {
  5647. // Create a use element
  5648. use: wrapWithAttrCheck(function (element, file) {
  5649. return this.put(new Use()).use(element, file);
  5650. })
  5651. }
  5652. });
  5653. register(Use, 'Use');
  5654. /* Optional Modules */
  5655. const SVG = makeInstance;
  5656. extend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox'));
  5657. extend([Line, Polyline, Polygon, Path], getMethodsFor('marker'));
  5658. extend(Text, getMethodsFor('Text'));
  5659. extend(Path, getMethodsFor('Path'));
  5660. extend(Defs, getMethodsFor('Defs'));
  5661. extend([Text, Tspan], getMethodsFor('Tspan'));
  5662. extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'));
  5663. extend(EventTarget, getMethodsFor('EventTarget'));
  5664. extend(Dom, getMethodsFor('Dom'));
  5665. extend(Element, getMethodsFor('Element'));
  5666. extend(Shape, getMethodsFor('Shape'));
  5667. extend([Container, Fragment], getMethodsFor('Container'));
  5668. extend(Gradient, getMethodsFor('Gradient'));
  5669. extend(Runner, getMethodsFor('Runner'));
  5670. List.extend(getMethodNames());
  5671. registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray, Point]);
  5672. makeMorphable();
  5673. export { A, Animator, SVGArray as Array, Box, Circle, ClipPath, Color, Container, Controller, Defs, Dom, Ease, Element, Ellipse, EventTarget, ForeignObject, Fragment, G, Gradient, Image, Line, List, Marker, Mask, Matrix, Morphable, NonMorphable, SVGNumber as Number, ObjectBag, PID, Path, PathArray, Pattern, Point, PointArray, Polygon, Polyline, Queue, Rect, Runner, SVG, Shape, Spring, Stop, Style, Svg, Symbol, Text, TextPath, Timeline, TransformBag, Tspan, Use, adopt, assignNewId, clearEvents, create, defaults, dispatch, easing, eid, extend, baseFind as find, getClass, getEventTarget, getEvents, getWindow, makeInstance, makeMorphable, mockAdopt, namespaces, nodeOrNew, off, on, parser, regex, register, registerMorphableType, registerWindow, restoreWindow, root, saveWindow, utils, windowEvents, withWindow, wrapWithAttrCheck };
  5674. //# sourceMappingURL=svg.esm.js.map