cytoscape.js 414 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934
  1. /* cytoscape.js */
  2. /**
  3. * This file is part of cytoscape.js 2.0.2.
  4. *
  5. * Cytoscape.js is free software: you can redistribute it and/or modify it
  6. * under the terms of the GNU Lesser General Public License as published by the Free
  7. * Software Foundation, either version 3 of the License, or (at your option) any
  8. * later version.
  9. *
  10. * Cytoscape.js is distributed in the hope that it will be useful, but WITHOUT
  11. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License along with
  16. * cytoscape.js. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. // this is put as a global var in the browser
  19. // or it's just a global to this module if commonjs
  20. var cytoscape;
  21. (function(){
  22. // the object iteself is a function that init's an instance of cytoscape
  23. var $$ = cytoscape = function(){
  24. return cytoscape.init.apply(cytoscape, arguments);
  25. };
  26. // allow functional access to cytoscape.js
  27. // e.g. var cyto = $.cytoscape({ selector: "#foo", ... });
  28. // var nodes = cyto.nodes();
  29. $$.init = function( options ){
  30. // if no options specified, use default
  31. if( options === undefined ){
  32. options = {};
  33. }
  34. // create instance
  35. if( $$.is.plainObject( options ) ){
  36. return new $$.Core( options );
  37. }
  38. // allow for registration of extensions
  39. // e.g. $.cytoscape("renderer", "svg", SvgRenderer);
  40. // e.g. $.cytoscape("renderer", "svg", "nodeshape", "ellipse", SvgEllipseNodeShape);
  41. // e.g. $.cytoscape("core", "doSomething", function(){ /* doSomething code */ });
  42. // e.g. $.cytoscape("collection", "doSomething", function(){ /* doSomething code */ });
  43. else if( $$.is.string( options ) ) {
  44. return $$.extension.apply($$.extension, arguments);
  45. }
  46. };
  47. // define the function namespace here, since it has members in many places
  48. $$.fn = {};
  49. // TODO test that this works:
  50. if( typeof exports !== 'undefined' ){ // expose as a commonjs module
  51. exports = module.exports = cytoscape;
  52. }
  53. // make sure we always register in the window just in case (e.g. w/ derbyjs)
  54. window.cytoscape = cytoscape;
  55. })();
  56. // type testing utility functions
  57. ;(function($$){
  58. $$.is = {
  59. string: function(obj){
  60. return obj != null && typeof obj == typeof "";
  61. },
  62. fn: function(obj){
  63. return obj != null && typeof obj === typeof function(){};
  64. },
  65. array: function(obj){
  66. return obj != null && obj instanceof Array;
  67. },
  68. plainObject: function(obj){
  69. return obj != null && typeof obj === typeof {} && !$$.is.array(obj) && obj.constructor === Object;
  70. },
  71. number: function(obj){
  72. return obj != null && typeof obj === typeof 1 && !isNaN(obj);
  73. },
  74. integer: function( obj ){
  75. return $$.is.number(obj) && Math.floor(obj) === obj;
  76. },
  77. color: function(obj){
  78. return obj != null && typeof obj === typeof "" && $.Color(obj).toString() !== "";
  79. },
  80. bool: function(obj){
  81. return obj != null && typeof obj === typeof true;
  82. },
  83. elementOrCollection: function(obj){
  84. return $$.is.element(obj) || $$.is.collection(obj);
  85. },
  86. element: function(obj){
  87. return obj instanceof $$.Element && obj._private.single;
  88. },
  89. collection: function(obj){
  90. return obj instanceof $$.Collection && !obj._private.single;
  91. },
  92. core: function(obj){
  93. return obj instanceof $$.Core;
  94. },
  95. style: function(obj){
  96. return obj instanceof $$.Style;
  97. },
  98. stylesheet: function(obj){
  99. return obj instanceof $$.Stylesheet;
  100. },
  101. event: function(obj){
  102. return obj instanceof $$.Event;
  103. },
  104. emptyString: function(obj){
  105. if( !obj ){ // null is empty
  106. return true;
  107. } else if( $$.is.string(obj) ){
  108. if( obj === "" || obj.match(/^\s+$/) ){
  109. return true; // empty string is empty
  110. }
  111. }
  112. return false; // otherwise, we don't know what we've got
  113. },
  114. nonemptyString: function(obj){
  115. if( obj && $$.is.string(obj) && obj !== "" && !obj.match(/^\s+$/) ){
  116. return true;
  117. }
  118. return false;
  119. },
  120. domElement: function(obj){
  121. if( typeof HTMLElement === 'undefined' ){
  122. return false; // we're not in a browser so it doesn't matter
  123. } else {
  124. return obj instanceof HTMLElement;
  125. }
  126. }
  127. };
  128. })( cytoscape );
  129. ;(function($$){
  130. // utility functions only for internal use
  131. $$.util = {
  132. // the jquery extend() function
  133. // NB: modified to use $$.is etc since we can't use jquery functions
  134. extend: function() {
  135. var options, name, src, copy, copyIsArray, clone,
  136. target = arguments[0] || {},
  137. i = 1,
  138. length = arguments.length,
  139. deep = false;
  140. // Handle a deep copy situation
  141. if ( typeof target === "boolean" ) {
  142. deep = target;
  143. target = arguments[1] || {};
  144. // skip the boolean and the target
  145. i = 2;
  146. }
  147. // Handle case when target is a string or something (possible in deep copy)
  148. if ( typeof target !== "object" && !$$.is.fn(target) ) {
  149. target = {};
  150. }
  151. // extend jQuery itself if only one argument is passed
  152. if ( length === i ) {
  153. target = this;
  154. --i;
  155. }
  156. for ( ; i < length; i++ ) {
  157. // Only deal with non-null/undefined values
  158. if ( (options = arguments[ i ]) != null ) {
  159. // Extend the base object
  160. for ( name in options ) {
  161. src = target[ name ];
  162. copy = options[ name ];
  163. // Prevent never-ending loop
  164. if ( target === copy ) {
  165. continue;
  166. }
  167. // Recurse if we're merging plain objects or arrays
  168. if ( deep && copy && ( $$.is.plainObject(copy) || (copyIsArray = $$.is.array(copy)) ) ) {
  169. if ( copyIsArray ) {
  170. copyIsArray = false;
  171. clone = src && $$.is.array(src) ? src : [];
  172. } else {
  173. clone = src && $$.is.plainObject(src) ? src : {};
  174. }
  175. // Never move original objects, clone them
  176. target[ name ] = $$.util.extend( deep, clone, copy );
  177. // Don't bring in undefined values
  178. } else if ( copy !== undefined ) {
  179. target[ name ] = copy;
  180. }
  181. }
  182. }
  183. }
  184. // Return the modified object
  185. return target;
  186. },
  187. error: function( msg ){
  188. if( console ){
  189. if( console.error ){
  190. console.error( msg );
  191. } else if( console.log ){
  192. console.log( msg );
  193. } else {
  194. throw msg;
  195. }
  196. } else {
  197. throw msg;
  198. }
  199. },
  200. clone: function( obj ){
  201. var target = {};
  202. for (var i in obj) {
  203. if ( obj.hasOwnProperty(i) ) { // TODO is this hasOwnProperty() call necessary for our use?
  204. target[i] = obj[i];
  205. }
  206. }
  207. return target;
  208. },
  209. // gets a shallow copy of the argument
  210. copy: function( obj ){
  211. if( obj == null ){
  212. return obj;
  213. } if( $$.is.array(obj) ){
  214. return obj.slice();
  215. } else if( $$.is.plainObject(obj) ){
  216. return $$.util.clone( obj );
  217. } else {
  218. return obj;
  219. }
  220. },
  221. // has anything been set in the map
  222. mapEmpty: function( map ){
  223. var empty = true;
  224. if( map != null ){
  225. for(var i in map){
  226. empty = false;
  227. break;
  228. }
  229. }
  230. return empty;
  231. },
  232. // pushes to the array at the end of a map (map may not be built)
  233. pushMap: function( options ){
  234. var array = $$.util.getMap(options);
  235. if( array == null ){ // if empty, put initial array
  236. $$.util.setMap( $.extend({}, options, {
  237. value: [ options.value ]
  238. }) );
  239. } else {
  240. array.push( options.value );
  241. }
  242. },
  243. // sets the value in a map (map may not be built)
  244. setMap: function( options ){
  245. var obj = options.map;
  246. var key;
  247. var keys = options.keys;
  248. var l = keys.length;
  249. for(var i = 0; i < l; i++){
  250. var key = keys[i];
  251. if( $$.is.plainObject( key ) ){
  252. $$.util.error("Tried to set map with object key");
  253. }
  254. if( i < keys.length - 1 ){
  255. // extend the map if necessary
  256. if( obj[key] == null ){
  257. obj[key] = {};
  258. }
  259. obj = obj[key];
  260. } else {
  261. // set the value
  262. obj[key] = options.value;
  263. }
  264. }
  265. },
  266. // gets the value in a map even if it's not built in places
  267. getMap: function( options ){
  268. var obj = options.map;
  269. var keys = options.keys;
  270. var l = keys.length;
  271. for(var i = 0; i < l; i++){
  272. var key = keys[i];
  273. if( $$.is.plainObject( key ) ){
  274. $$.util.error("Tried to get map with object key");
  275. }
  276. obj = obj[key];
  277. if( obj == null ){
  278. return obj;
  279. }
  280. }
  281. return obj;
  282. },
  283. // deletes the entry in the map
  284. deleteMap: function( options ){
  285. var obj = options.map;
  286. var keys = options.keys;
  287. var l = keys.length;
  288. var keepChildren = options.keepChildren;
  289. for(var i = 0; i < l; i++){
  290. var key = keys[i];
  291. if( $$.is.plainObject( key ) ){
  292. $$.util.error("Tried to delete map with object key");
  293. }
  294. var lastKey = i === options.keys.length - 1;
  295. if( lastKey ){
  296. if( keepChildren ){ // then only delete child fields not in keepChildren
  297. for( var child in obj ){
  298. if( !keepChildren[child] ){
  299. delete obj[child];
  300. }
  301. }
  302. } else {
  303. delete obj[key];
  304. }
  305. } else {
  306. obj = obj[key];
  307. }
  308. }
  309. },
  310. capitalize: function(str){
  311. if( $$.is.emptyString(str) ){
  312. return str;
  313. }
  314. return str.charAt(0).toUpperCase() + str.substring(1);
  315. },
  316. camel2dash: function( str ){
  317. var ret = [];
  318. for( var i = 0; i < str.length; i++ ){
  319. var ch = str[i];
  320. var chLowerCase = ch.toLowerCase();
  321. var isUpperCase = ch !== chLowerCase;
  322. if( isUpperCase ){
  323. ret.push( "-" );
  324. ret.push( chLowerCase );
  325. } else {
  326. ret.push( ch );
  327. }
  328. }
  329. var noUpperCases = ret.length === str.length;
  330. if( noUpperCases ){ return str } // cheaper than .join()
  331. return ret.join("");
  332. },
  333. dash2camel: function( str ){
  334. var ret = [];
  335. var nextIsUpper = false;
  336. for( var i = 0; i < str.length; i++ ){
  337. var ch = str[i];
  338. var isDash = ch === "-";
  339. if( isDash ){
  340. nextIsUpper = true;
  341. } else {
  342. if( nextIsUpper ){
  343. ret.push( ch.toUpperCase() );
  344. } else {
  345. ret.push( ch );
  346. }
  347. nextIsUpper = false;
  348. }
  349. }
  350. return ret.join("");
  351. },
  352. // strip spaces from beginning of string and end of string
  353. trim: function( str ){
  354. var first, last;
  355. // find first non-space char
  356. for( first = 0; first < str.length && str[first] === " "; first++ ){}
  357. // find last non-space char
  358. for( last = str.length - 1; last > first && str[last] === " "; last-- ){}
  359. return str.substring(first, last + 1);
  360. },
  361. // get [r, g, b] from #abc or #aabbcc
  362. hex2tuple: function( hex ){
  363. if( !(hex.length === 4 || hex.length === 7) || hex[0] !== "#" ){ return; }
  364. var shortHex = hex.length === 4;
  365. var r, g, b;
  366. var base = 16;
  367. if( shortHex ){
  368. r = parseInt( hex[1] + hex[1], base );
  369. g = parseInt( hex[2] + hex[2], base );
  370. b = parseInt( hex[3] + hex[3], base );
  371. } else {
  372. r = parseInt( hex[1] + hex[2], base );
  373. g = parseInt( hex[3] + hex[4], base );
  374. b = parseInt( hex[5] + hex[6], base );
  375. }
  376. return [r, g, b];
  377. },
  378. // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0)
  379. hsl2tuple: function( hsl ){
  380. var ret;
  381. var number = $$.util.regex.number;
  382. var h, s, l, a, r, g, b;
  383. var m = new RegExp("^" + $$.util.regex.hsla + "$").exec(hsl);
  384. if( m ){
  385. // get hue
  386. h = parseInt( m[1] );
  387. if( h < 0 ){
  388. h = ( 360 - (-1*h % 360) ) % 360;
  389. } else if( h > 360 ){
  390. h = h % 360;
  391. }
  392. h /= 360; // normalise on [0, 1]
  393. s = parseFloat( m[2] );
  394. if( s < 0 || s > 100 ){ return; } // saturation is [0, 100]
  395. s = s/100; // normalise on [0, 1]
  396. l = parseFloat( m[3] );
  397. if( l < 0 || l > 100 ){ return; } // lightness is [0, 100]
  398. l = l/100; // normalise on [0, 1]
  399. a = m[4];
  400. if( a !== undefined ){
  401. a = parseFloat( a );
  402. if( a < 0 || a > 1 ){ return; } // alpha is [0, 1]
  403. }
  404. // now, convert to rgb
  405. // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
  406. if( s === 0 ){
  407. r = g = b = Math.round(l * 255); // achromatic
  408. } else {
  409. function hue2rgb(p, q, t){
  410. if(t < 0) t += 1;
  411. if(t > 1) t -= 1;
  412. if(t < 1/6) return p + (q - p) * 6 * t;
  413. if(t < 1/2) return q;
  414. if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  415. return p;
  416. }
  417. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  418. var p = 2 * l - q;
  419. r = Math.round( 255 * hue2rgb(p, q, h + 1/3) );
  420. g = Math.round( 255 * hue2rgb(p, q, h) );
  421. b = Math.round( 255 * hue2rgb(p, q, h - 1/3) );
  422. }
  423. ret = [r, g, b, a];
  424. }
  425. return ret;
  426. },
  427. // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0)
  428. rgb2tuple: function( rgb ){
  429. var ret;
  430. var number = $$.util.regex.number;
  431. var m = new RegExp("^" + $$.util.regex.rgba + "$").exec(rgb);
  432. if( m ){
  433. ret = [];
  434. var isPct = [];
  435. for( var i = 1; i <= 3; i++ ){
  436. var channel = m[i];
  437. if( channel[ channel.length - 1 ] === "%" ){
  438. isPct[i] = true;
  439. }
  440. channel = parseFloat( channel );
  441. if( isPct[i] ){
  442. channel = channel/100 * 255; // normalise to [0, 255]
  443. }
  444. if( channel < 0 || channel > 255 ){ return; } // invalid channel value
  445. ret.push( Math.floor(channel) );
  446. }
  447. var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3];
  448. var allArePct = isPct[1] && isPct[2] && isPct[3];
  449. if( atLeastOneIsPct && !allArePct ){ return; } // must all be percent values if one is
  450. var alpha = m[4];
  451. if( alpha !== undefined ){
  452. alpha = parseFloat( alpha );
  453. if( alpha < 0 || alpha > 1 ){ return; } // invalid alpha value
  454. ret.push( alpha );
  455. }
  456. }
  457. return ret;
  458. },
  459. colorname2tuple: function( color ){
  460. return $$.util.colors[ color.toLowerCase() ];
  461. },
  462. color2tuple: function( color ){
  463. return $$.util.colorname2tuple(color)
  464. || $$.util.hex2tuple(color)
  465. || $$.util.rgb2tuple(color)
  466. || $$.util.hsl2tuple(color);
  467. },
  468. tuple2hex: function( tuple ){
  469. var r = tuple[0];
  470. var g = tuple[1];
  471. var b = tuple[2];
  472. function ch2hex( ch ){
  473. var hex = ch.toString(16);
  474. if( hex.length === 1 ){
  475. hex = '0' + hex;
  476. }
  477. return hex;
  478. }
  479. return '#' + ch2hex(r) + ch2hex(g) + ch2hex(b);
  480. },
  481. colors: {
  482. // special colour names
  483. transparent: [0,0,0,0], // NB alpha === 0
  484. // regular colours
  485. aliceblue: [240,248,255],
  486. antiquewhite: [250,235,215],
  487. aqua: [0,255,255],
  488. aquamarine: [127,255,212],
  489. azure: [240,255,255],
  490. beige: [245,245,220],
  491. bisque: [255,228,196],
  492. black: [0,0,0],
  493. blanchedalmond: [255,235,205],
  494. blue: [0,0,255],
  495. blueviolet: [138,43,226],
  496. brown: [165,42,42],
  497. burlywood: [222,184,135],
  498. cadetblue: [95,158,160],
  499. chartreuse: [127,255,0],
  500. chocolate: [210,105,30],
  501. coral: [255,127,80],
  502. cornflowerblue: [100,149,237],
  503. cornsilk: [255,248,220],
  504. crimson: [220,20,60],
  505. cyan: [0,255,255],
  506. darkblue: [0,0,139],
  507. darkcyan: [0,139,139],
  508. darkgoldenrod: [184,134,11],
  509. darkgray: [169,169,169],
  510. darkgreen: [0,100,0],
  511. darkgrey: [169,169,169],
  512. darkkhaki: [189,183,107],
  513. darkmagenta: [139,0,139],
  514. darkolivegreen: [85,107,47],
  515. darkorange: [255,140,0],
  516. darkorchid: [153,50,204],
  517. darkred: [139,0,0],
  518. darksalmon: [233,150,122],
  519. darkseagreen: [143,188,143],
  520. darkslateblue: [72,61,139],
  521. darkslategray: [47,79,79],
  522. darkslategrey: [47,79,79],
  523. darkturquoise: [0,206,209],
  524. darkviolet: [148,0,211],
  525. deeppink: [255,20,147],
  526. deepskyblue: [0,191,255],
  527. dimgray: [105,105,105],
  528. dimgrey: [105,105,105],
  529. dodgerblue: [30,144,255],
  530. firebrick: [178,34,34],
  531. floralwhite: [255,250,240],
  532. forestgreen: [34,139,34],
  533. fuchsia: [255,0,255],
  534. gainsboro: [220,220,220],
  535. ghostwhite: [248,248,255],
  536. gold: [255,215,0],
  537. goldenrod: [218,165,32],
  538. gray: [128,128,128],
  539. grey: [128,128,128],
  540. green: [0,128,0],
  541. greenyellow: [173,255,47],
  542. honeydew: [240,255,240],
  543. hotpink: [255,105,180],
  544. indianred: [205,92,92],
  545. indigo: [75,0,130],
  546. ivory: [255,255,240],
  547. khaki: [240,230,140],
  548. lavender: [230,230,250],
  549. lavenderblush: [255,240,245],
  550. lawngreen: [124,252,0],
  551. lemonchiffon: [255,250,205],
  552. lightblue: [173,216,230],
  553. lightcoral: [240,128,128],
  554. lightcyan: [224,255,255],
  555. lightgoldenrodyellow: [250,250,210],
  556. lightgray: [211,211,211],
  557. lightgreen: [144,238,144],
  558. lightgrey: [211,211,211],
  559. lightpink: [255,182,193],
  560. lightsalmon: [255,160,122],
  561. lightseagreen: [32,178,170],
  562. lightskyblue: [135,206,250],
  563. lightslategray: [119,136,153],
  564. lightslategrey: [119,136,153],
  565. lightsteelblue: [176,196,222],
  566. lightyellow: [255,255,224],
  567. lime: [0,255,0],
  568. limegreen: [50,205,50],
  569. linen: [250,240,230],
  570. magenta: [255,0,255],
  571. maroon: [128,0,0],
  572. mediumaquamarine: [102,205,170],
  573. mediumblue: [0,0,205],
  574. mediumorchid: [186,85,211],
  575. mediumpurple: [147,112,219],
  576. mediumseagreen: [60,179,113],
  577. mediumslateblue: [123,104,238],
  578. mediumspringgreen: [0,250,154],
  579. mediumturquoise: [72,209,204],
  580. mediumvioletred: [199,21,133],
  581. midnightblue: [25,25,112],
  582. mintcream: [245,255,250],
  583. mistyrose: [255,228,225],
  584. moccasin: [255,228,181],
  585. navajowhite: [255,222,173],
  586. navy: [0,0,128],
  587. oldlace: [253,245,230],
  588. olive: [128,128,0],
  589. olivedrab: [107,142,35],
  590. orange: [255,165,0],
  591. orangered: [255,69,0],
  592. orchid: [218,112,214],
  593. palegoldenrod: [238,232,170],
  594. palegreen: [152,251,152],
  595. paleturquoise: [175,238,238],
  596. palevioletred: [219,112,147],
  597. papayawhip: [255,239,213],
  598. peachpuff: [255,218,185],
  599. peru: [205,133,63],
  600. pink: [255,192,203],
  601. plum: [221,160,221],
  602. powderblue: [176,224,230],
  603. purple: [128,0,128],
  604. red: [255,0,0],
  605. rosybrown: [188,143,143],
  606. royalblue: [65,105,225],
  607. saddlebrown: [139,69,19],
  608. salmon: [250,128,114],
  609. sandybrown: [244,164,96],
  610. seagreen: [46,139,87],
  611. seashell: [255,245,238],
  612. sienna: [160,82,45],
  613. silver: [192,192,192],
  614. skyblue: [135,206,235],
  615. slateblue: [106,90,205],
  616. slategray: [112,128,144],
  617. slategrey: [112,128,144],
  618. snow: [255,250,250],
  619. springgreen: [0,255,127],
  620. steelblue: [70,130,180],
  621. tan: [210,180,140],
  622. teal: [0,128,128],
  623. thistle: [216,191,216],
  624. tomato: [255,99,71],
  625. turquoise: [64,224,208],
  626. violet: [238,130,238],
  627. wheat: [245,222,179],
  628. white: [255,255,255],
  629. whitesmoke: [245,245,245],
  630. yellow: [255,255,0],
  631. yellowgreen: [154,205,50]
  632. }
  633. };
  634. $$.util.regex = {};
  635. $$.util.regex.number = "(?:\\d*\\.\\d+|\\d+|\\d*\\.\\d+[eE]\\d+)";
  636. $$.util.regex.rgba = "rgb[a]?\\(("+ $$.util.regex.number +"[%]?)\\s*,\\s*("+ $$.util.regex.number +"[%]?)\\s*,\\s*("+ $$.util.regex.number +"[%]?)(?:\\s*,\\s*("+ $$.util.regex.number +"))?\\)";
  637. $$.util.regex.rgbaNoBackRefs = "rgb[a]?\\((?:"+ $$.util.regex.number +"[%]?)\\s*,\\s*(?:"+ $$.util.regex.number +"[%]?)\\s*,\\s*(?:"+ $$.util.regex.number +"[%]?)(?:\\s*,\\s*(?:"+ $$.util.regex.number +"))?\\)";
  638. $$.util.regex.hsla = "hsl[a]?\\(("+ $$.util.regex.number +")\\s*,\\s*("+ $$.util.regex.number +"[%])\\s*,\\s*("+ $$.util.regex.number +"[%])(?:\\s*,\\s*("+ $$.util.regex.number +"))?\\)";
  639. $$.util.regex.hslaNoBackRefs = "hsl[a]?\\((?:"+ $$.util.regex.number +")\\s*,\\s*(?:"+ $$.util.regex.number +"[%])\\s*,\\s*(?:"+ $$.util.regex.number +"[%])(?:\\s*,\\s*(?:"+ $$.util.regex.number +"))?\\)";
  640. $$.util.regex.hex3 = "\\#[0-9a-fA-F]{3}";
  641. $$.util.regex.hex6 = "\\#[0-9a-fA-F]{6}";
  642. })( cytoscape );
  643. ;(function($$){
  644. $$.math = {};
  645. $$.math.boxInBezierVicinity = function(
  646. x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
  647. // Return values:
  648. // 0 - curve is not in box
  649. // 1 - curve may be in box; needs precise check
  650. // 2 - curve is in box
  651. var boxMinX = Math.min(x1box, x2box) - tolerance;
  652. var boxMinY = Math.min(y1box, y2box) - tolerance;
  653. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  654. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  655. if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) {
  656. return 2;
  657. } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) {
  658. return 2;
  659. } else if (x2 >= boxMinX && x2 <= boxMaxX && y2 >= boxMinY && y2 <= boxMaxY) {
  660. return 1;
  661. }
  662. var curveMinX = Math.min(x1, x2, x3);
  663. var curveMinY = Math.min(y1, y2, y3);
  664. var curveMaxX = Math.max(x1, x2, x3);
  665. var curveMaxY = Math.max(y1, y2, y3);
  666. /*
  667. console.log(curveMinX + ", " + curveMinY + ", " + curveMaxX
  668. + ", " + curveMaxY);
  669. if (curveMinX == undefined) {
  670. console.log("undefined curveMinX: " + x1 + ", " + x2 + ", " + x3);
  671. }
  672. */
  673. if (curveMinX > boxMaxX
  674. || curveMaxX < boxMinX
  675. || curveMinY > boxMaxY
  676. || curveMaxY < boxMinY) {
  677. return 0;
  678. }
  679. return 1;
  680. }
  681. $$.math.checkStraightEdgeCrossesBox = function(
  682. x1box, y1box, x2box, y2box, x1, y1, x2, y2, tolerance) {
  683. var boxMinX = Math.min(x1box, x2box) - tolerance;
  684. var boxMinY = Math.min(y1box, y2box) - tolerance;
  685. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  686. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  687. // Check left + right bounds
  688. var aX = x2 - x1;
  689. var bX = x1;
  690. var yValue;
  691. // Top and bottom
  692. var aY = y2 - y1;
  693. var bY = y1;
  694. var xValue;
  695. if (Math.abs(aX) < 0.0001) {
  696. return (x1 >= boxMinX && x1 <= boxMaxX
  697. && Math.min(y1, y2) <= boxMinY
  698. && Math.max(y1, y2) >= boxMaxY);
  699. }
  700. var tLeft = (boxMinX - bX) / aX;
  701. if (tLeft > 0 && tLeft <= 1) {
  702. yValue = aY * tLeft + bY;
  703. if (yValue >= boxMinY && yValue <= boxMaxY) {
  704. return true;
  705. }
  706. }
  707. var tRight = (boxMaxX - bX) / aX;
  708. if (tRight > 0 && tRight <= 1) {
  709. yValue = aY * tRight + bY;
  710. if (yValue >= boxMinY && yValue <= boxMaxY) {
  711. return true;
  712. }
  713. }
  714. var tTop = (boxMinY - bY) / aY;
  715. if (tTop > 0 && tTop <= 1) {
  716. xValue = aX * tTop + bX;
  717. if (xValue >= boxMinX && xValue <= boxMaxX) {
  718. return true;
  719. }
  720. }
  721. var tBottom = (boxMaxY - bY) / aY;
  722. if (tBottom > 0 && tBottom <= 1) {
  723. xValue = aX * tBottom + bX;
  724. if (xValue >= boxMinX && xValue <= boxMaxX) {
  725. return true;
  726. }
  727. }
  728. return false;
  729. }
  730. $$.math.checkBezierCrossesBox = function(
  731. x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
  732. var boxMinX = Math.min(x1box, x2box) - tolerance;
  733. var boxMinY = Math.min(y1box, y2box) - tolerance;
  734. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  735. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  736. if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) {
  737. return true;
  738. } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) {
  739. return true;
  740. }
  741. var aX = x1 - 2 * x2 + x3;
  742. var bX = -2 * x1 + 2 * x2;
  743. var cX = x1;
  744. var xIntervals = [];
  745. if (Math.abs(aX) < 0.0001) {
  746. var leftParam = (boxMinX - x1) / bX;
  747. var rightParam = (boxMaxX - x1) / bX;
  748. xIntervals.push(leftParam, rightParam);
  749. } else {
  750. // Find when x coordinate of the curve crosses the left side of the box
  751. var discriminantX1 = bX * bX - 4 * aX * (cX - boxMinX);
  752. var tX1, tX2;
  753. if (discriminantX1 > 0) {
  754. var sqrt = Math.sqrt(discriminantX1);
  755. tX1 = (-bX + sqrt) / (2 * aX);
  756. tX2 = (-bX - sqrt) / (2 * aX);
  757. xIntervals.push(tX1, tX2);
  758. }
  759. var discriminantX2 = bX * bX - 4 * aX * (cX - boxMaxX);
  760. var tX3, tX4;
  761. if (discriminantX2 > 0) {
  762. var sqrt = Math.sqrt(discriminantX2);
  763. tX3 = (-bX + sqrt) / (2 * aX);
  764. tX4 = (-bX - sqrt) / (2 * aX);
  765. xIntervals.push(tX3, tX4);
  766. }
  767. }
  768. xIntervals.sort(function(a, b) { return a - b; });
  769. var aY = y1 - 2 * y2 + y3;
  770. var bY = -2 * y1 + 2 * y2;
  771. var cY = y1;
  772. var yIntervals = [];
  773. if (Math.abs(aY) < 0.0001) {
  774. var topParam = (boxMinY - y1) / bY;
  775. var bottomParam = (boxMaxY - y1) / bY;
  776. yIntervals.push(topParam, bottomParam);
  777. } else {
  778. var discriminantY1 = bY * bY - 4 * aY * (cY - boxMinY);
  779. var tY1, tY2;
  780. if (discriminantY1 > 0) {
  781. var sqrt = Math.sqrt(discriminantY1);
  782. tY1 = (-bY + sqrt) / (2 * aY);
  783. tY2 = (-bY - sqrt) / (2 * aY);
  784. yIntervals.push(tY1, tY2);
  785. }
  786. var discriminantY2 = bY * bY - 4 * aY * (cY - boxMaxY);
  787. var tY3, tY4;
  788. if (discriminantY2 > 0) {
  789. var sqrt = Math.sqrt(discriminantY2);
  790. tY3 = (-bY + sqrt) / (2 * aY);
  791. tY4 = (-bY - sqrt) / (2 * aY);
  792. yIntervals.push(tY3, tY4);
  793. }
  794. }
  795. yIntervals.sort(function(a, b) { return a - b; });
  796. for (var index = 0; index < xIntervals.length; index += 2) {
  797. for (var yIndex = 1; yIndex < yIntervals.length; yIndex += 2) {
  798. // Check if there exists values for the Bezier curve
  799. // parameter between 0 and 1 where both the curve's
  800. // x and y coordinates are within the bounds specified by the box
  801. if (xIntervals[index] < yIntervals[yIndex]
  802. && yIntervals[yIndex] >= 0.0
  803. && xIntervals[index] <= 1.0
  804. && xIntervals[index + 1] > yIntervals[yIndex - 1]
  805. && yIntervals[yIndex - 1] <= 1.0
  806. && xIntervals[index + 1] >= 0.0) {
  807. return true;
  808. }
  809. }
  810. }
  811. return false;
  812. }
  813. $$.math.inBezierVicinity = function(
  814. x, y, x1, y1, x2, y2, x3, y3, toleranceSquared) {
  815. // Middle point occurs when t = 0.5, this is when the Bezier
  816. // is closest to (x2, y2)
  817. var middlePointX = 0.25 * x1 + 0.5 * x2 + 0.25 * x3;
  818. var middlePointY = 0.25 * y1 + 0.5 * y2 + 0.25 * y3;
  819. var displacementX, displacementY, offsetX, offsetY;
  820. var dotProduct, dotSquared, hypSquared;
  821. var outside = function(x, y, startX, startY, endX, endY,
  822. toleranceSquared, counterClockwise) {
  823. dotProduct = (endY - startY) * (x - startX) + (startX - endX) * (y - startY);
  824. dotSquared = dotProduct * dotProduct;
  825. sideSquared = (endY - startY) * (endY - startY)
  826. + (startX - endX) * (startX - endX);
  827. if (counterClockwise) {
  828. if (dotProduct > 0) {
  829. return false;
  830. }
  831. } else {
  832. if (dotProduct < 0) {
  833. return false;
  834. }
  835. }
  836. return (dotSquared / sideSquared > toleranceSquared);
  837. };
  838. // Used to check if the test polygon winding is clockwise or counterclockwise
  839. var testPointX = (middlePointX + x2) / 2.0;
  840. var testPointY = (middlePointY + y2) / 2.0;
  841. var counterClockwise = true;
  842. // The test point is always inside
  843. if (outside(testPointX, testPointY, x1, y1, x2, y2, 0, counterClockwise)) {
  844. counterClockwise = !counterClockwise;
  845. }
  846. /*
  847. return (!outside(x, y, x1, y1, x2, y2, toleranceSquared, counterClockwise)
  848. && !outside(x, y, x2, y2, x3, y3, toleranceSquared, counterClockwise)
  849. && !outside(x, y, x3, y3, middlePointX, middlePointY, toleranceSquared,
  850. counterClockwise)
  851. && !outside(x, y, middlePointX, middlePointY, x1, y1, toleranceSquared,
  852. counterClockwise)
  853. );
  854. */
  855. return (!outside(x, y, x1, y1, x2, y2, toleranceSquared, counterClockwise)
  856. && !outside(x, y, x2, y2, x3, y3, toleranceSquared, counterClockwise)
  857. && !outside(x, y, x3, y3, x1, y1, toleranceSquared,
  858. counterClockwise)
  859. );
  860. }
  861. // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where
  862. // r is the real component, i is the imaginary component
  863. $$.math.solveCubic = function(a, b, c, d, result) {
  864. // An implementation of the Cardano method
  865. // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots
  866. // provided by http://www3.telus.net/thothworks/Quad3Deg.html
  867. // Get rid of a
  868. b /= a;
  869. c /= a;
  870. d /= a;
  871. var discrim, q, r, dum1, s, t, term1, r13;
  872. q = (3.0 * c - (b * b)) / 9.0;
  873. r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b));
  874. r /= 54.0;
  875. discrim = q * q * q + r * r;
  876. result[1] = 0; //The first root is always real.
  877. term1 = (b / 3.0);
  878. if (discrim > 0) { // one root real, two are complex
  879. s = r + Math.sqrt(discrim);
  880. s = ((s < 0) ? -Math.pow(-s, (1.0 / 3.0)) : Math.pow(s, (1.0 / 3.0)));
  881. t = r - Math.sqrt(discrim);
  882. t = ((t < 0) ? -Math.pow(-t, (1.0 / 3.0)) : Math.pow(t, (1.0 / 3.0)));
  883. result[0] = -term1 + s + t;
  884. term1 += (s + t) / 2.0;
  885. result[4] = result[2] = -term1;
  886. term1 = Math.sqrt(3.0) * (-t + s) / 2;
  887. result[3] = term1;
  888. result[5] = -term1;
  889. return;
  890. } // End if (discrim > 0)
  891. // The remaining options are all real
  892. result[5] = result[3] = 0;
  893. if (discrim == 0){ // All roots real, at least two are equal.
  894. r13 = ((r < 0) ? -Math.pow(-r, (1.0 / 3.0)) : Math.pow(r, (1.0 / 3.0)));
  895. result[0] = -term1 + 2.0 * r13;
  896. result[4] = result[2] = -(r13 + term1);
  897. return;
  898. } // End if (discrim == 0)
  899. // Only option left is that all roots are real and unequal (to get here, q < 0)
  900. q = -q;
  901. dum1 = q * q * q;
  902. dum1 = Math.acos(r / Math.sqrt(dum1));
  903. r13 = 2.0 * Math.sqrt(q);
  904. result[0] = -term1 + r13 * Math.cos(dum1 / 3.0);
  905. result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0);
  906. result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0);
  907. return;
  908. }
  909. $$.math.sqDistanceToQuadraticBezier = function(x, y,
  910. x1, y1, x2, y2, x3, y3) {
  911. // Find minimum distance by using the minimum of the distance
  912. // function between the given point and the curve
  913. // This gives the coefficients of the resulting cubic equation
  914. // whose roots tell us where a possible minimum is
  915. // (Coefficients are divided by 4)
  916. var a = 1.0 * x1*x1 - 4*x1*x2 + 2*x1*x3 + 4*x2*x2 - 4*x2*x3 + x3*x3
  917. + y1*y1 - 4*y1*y2 + 2*y1*y3 + 4*y2*y2 - 4*y2*y3 + y3*y3;
  918. var b = 1.0 * 9*x1*x2 - 3*x1*x1 - 3*x1*x3 - 6*x2*x2 + 3*x2*x3
  919. + 9*y1*y2 - 3*y1*y1 - 3*y1*y3 - 6*y2*y2 + 3*y2*y3;
  920. var c = 1.0 * 3*x1*x1 - 6*x1*x2 + x1*x3 - x1*x + 2*x2*x2 + 2*x2*x - x3*x
  921. + 3*y1*y1 - 6*y1*y2 + y1*y3 - y1*y + 2*y2*y2 + 2*y2*y - y3*y;
  922. var d = 1.0 * x1*x2 - x1*x1 + x1*x - x2*x
  923. + y1*y2 - y1*y1 + y1*y - y2*y;
  924. debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a);
  925. var roots = [];
  926. // Use the cubic solving algorithm
  927. this.solveCubic(a, b, c, d, roots);
  928. var zeroThreshold = 0.0000001;
  929. var params = [];
  930. for (var index = 0; index < 6; index += 2) {
  931. if (Math.abs(roots[index + 1]) < zeroThreshold
  932. && roots[index] >= 0
  933. && roots[index] <= 1.0) {
  934. params.push(roots[index]);
  935. }
  936. }
  937. params.push(1.0);
  938. params.push(0.0);
  939. var minDistanceSquared = -1;
  940. var closestParam;
  941. var curX, curY, distSquared;
  942. for (var i = 0; i < params.length; i++) {
  943. curX = Math.pow(1.0 - params[i], 2.0) * x1
  944. + 2.0 * (1 - params[i]) * params[i] * x2
  945. + params[i] * params[i] * x3;
  946. curY = Math.pow(1 - params[i], 2.0) * y1
  947. + 2 * (1.0 - params[i]) * params[i] * y2
  948. + params[i] * params[i] * y3;
  949. distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2);
  950. debug("distance for param " + params[i] + ": " + Math.sqrt(distSquared));
  951. if (minDistanceSquared >= 0) {
  952. if (distSquared < minDistanceSquared) {
  953. minDistanceSquared = distSquared;
  954. closestParam = params[i];
  955. }
  956. } else {
  957. minDistanceSquared = distSquared;
  958. closestParam = params[i];
  959. }
  960. }
  961. /*
  962. debugStats.clickX = x;
  963. debugStats.clickY = y;
  964. debugStats.closestX = Math.pow(1.0 - closestParam, 2.0) * x1
  965. + 2.0 * (1.0 - closestParam) * closestParam * x2
  966. + closestParam * closestParam * x3;
  967. debugStats.closestY = Math.pow(1.0 - closestParam, 2.0) * y1
  968. + 2.0 * (1.0 - closestParam) * closestParam * y2
  969. + closestParam * closestParam * y3;
  970. */
  971. debug("given: "
  972. + "( " + x + ", " + y + "), "
  973. + "( " + x1 + ", " + y1 + "), "
  974. + "( " + x2 + ", " + y2 + "), "
  975. + "( " + x3 + ", " + y3 + ")");
  976. debug("roots: " + roots);
  977. debug("params: " + params);
  978. debug("closest param: " + closestParam);
  979. return minDistanceSquared;
  980. }
  981. var debug = function(o) {
  982. if (false) {
  983. console.log(o);
  984. }
  985. }
  986. })( cytoscape );
  987. // type testing utility functions
  988. ;(function($$){
  989. // list of ids with other metadata assoc'd with it
  990. $$.instances = [];
  991. $$.instanceCounter = 0;
  992. $$.lastInstanceTime;
  993. $$.registerInstance = function( instance, domElement ){
  994. var cy;
  995. if( $$.is.core(instance) ){
  996. cy = instance;
  997. } else if( $$.is.domElement(instance) ){
  998. domElement = instance;
  999. }
  1000. // if we have an old reg that is empty (no cy), then
  1001. var oldReg = $$.getRegistrationForInstance(instance, domElement);
  1002. if( oldReg ){
  1003. if( !oldReg.cy ){
  1004. oldReg.cy = instance;
  1005. oldReg.domElement = domElement;
  1006. } else {
  1007. $$.util.error('Tried to register on a pre-existing registration');
  1008. }
  1009. return oldReg;
  1010. // otherwise, just make a new registration
  1011. } else {
  1012. var time = +new Date;
  1013. var suffix;
  1014. // add a suffix in case instances collide on the same time
  1015. if( !$$.lastInstanceTime || $$.lastInstanceTime === time ){
  1016. $$.instanceCounter = 0;
  1017. } else {
  1018. ++$$.instanceCounter;
  1019. }
  1020. $$.lastInstanceTime = time;
  1021. suffix = $$.instanceCounter;
  1022. var id = "cy-" + time + "-" + suffix;
  1023. // create the registration object
  1024. var registration = {
  1025. id: id,
  1026. cy: cy,
  1027. domElement: domElement,
  1028. readies: [] // list of bound ready functions before calling init
  1029. };
  1030. // put the registration object in the pool
  1031. $$.instances.push( registration );
  1032. $$.instances[ id ] = registration;
  1033. return registration;
  1034. }
  1035. };
  1036. $$.removeRegistrationForInstance = function(instance, domElement){
  1037. var cy;
  1038. if( $$.is.core(instance) ){
  1039. cy = instance;
  1040. } else if( $$.is.domElement(instance) ){
  1041. domElement = instance;
  1042. }
  1043. if( $$.is.core(cy) ){
  1044. var id = cy._private.instanceId;
  1045. delete $$.instances[ id ];
  1046. $$.instances.splice(id, 1);
  1047. } else if( $$.is.domElement(domElement) ){
  1048. for( var i = 0; i < $$.instances.length; i++ ){
  1049. var reg = $$.instances[i];
  1050. if( reg.domElement === domElement ){
  1051. delete $$.instances[ reg.id ];
  1052. $$.instances.splice(i, 1);
  1053. i--;
  1054. }
  1055. }
  1056. }
  1057. }
  1058. $$.getRegistrationForInstance = function( instance, domElement ){
  1059. var cy;
  1060. if( $$.is.core(instance) ){
  1061. if( instance.registered() ){ // only want it if it's registered b/c if not it has no reg'd id
  1062. cy = instance;
  1063. }
  1064. } else if( $$.is.domElement(instance) ){
  1065. domElement = instance;
  1066. }
  1067. if( $$.is.core(cy) ){
  1068. var id = cy._private.instanceId;
  1069. return $$.instances[ id ];
  1070. } else if( $$.is.domElement(domElement) ){
  1071. for( var i = $$.instances.length - 1; i >= 0; i-- ){ // look backwards, since most recent is the one we want
  1072. var reg = $$.instances[i];
  1073. if( reg.domElement === domElement ){
  1074. return reg;
  1075. }
  1076. }
  1077. }
  1078. };
  1079. })( cytoscape );
  1080. ;(function($$){
  1081. // registered extensions to cytoscape, indexed by name
  1082. var extensions = {};
  1083. $$.extensions = extensions;
  1084. // registered modules for extensions, indexed by name
  1085. var modules = {};
  1086. $$.modules = modules;
  1087. function setExtension(type, name, registrant){
  1088. var impl = {};
  1089. impl[name] = registrant;
  1090. switch( type ){
  1091. case "core":
  1092. case "collection":
  1093. $$.fn[type]( impl );
  1094. }
  1095. return $$.util.setMap({
  1096. map: extensions,
  1097. keys: [ type, name ],
  1098. value: registrant
  1099. });
  1100. }
  1101. function getExtension(type, name){
  1102. return $$.util.getMap({
  1103. map: extensions,
  1104. keys: [ type, name ]
  1105. });
  1106. }
  1107. function setModule(type, name, moduleType, moduleName, registrant){
  1108. return $$.util.setMap({
  1109. map: modules,
  1110. keys: [ type, name, moduleType, moduleName ],
  1111. value: registrant
  1112. });
  1113. }
  1114. function getModule(type, name, moduleType, moduleName){
  1115. return $$.util.getMap({
  1116. map: modules,
  1117. keys: [ type, name, moduleType, moduleName ]
  1118. });
  1119. }
  1120. $$.extension = function(){
  1121. // e.g. $$.extension("renderer", "svg")
  1122. if( arguments.length == 2 ){
  1123. return getExtension.apply(this, arguments);
  1124. }
  1125. // e.g. $$.extension("renderer", "svg", { ... })
  1126. else if( arguments.length == 3 ){
  1127. return setExtension.apply(this, arguments);
  1128. }
  1129. // e.g. $$.extension("renderer", "svg", "nodeShape", "ellipse")
  1130. else if( arguments.length == 4 ){
  1131. return getModule.apply(this, arguments);
  1132. }
  1133. // e.g. $$.extension("renderer", "svg", "nodeShape", "ellipse", { ... })
  1134. else if( arguments.length == 5 ){
  1135. return setModule.apply(this, arguments);
  1136. }
  1137. else {
  1138. $.error("Invalid extension access syntax");
  1139. }
  1140. };
  1141. })( cytoscape );
  1142. ;(function($, $$){
  1143. if( !$ ){ return } // no jquery => don't need this
  1144. // allow calls on a jQuery selector by proxying calls to $.cytoscape
  1145. // e.g. $("#foo").cytoscape(options) => $.cytoscape(options) on #foo
  1146. $.fn.cytoscape = function(opts){
  1147. var $this = $(this);
  1148. // get object
  1149. if( opts === "get" ){
  1150. var reg = $$.getRegistrationForInstance( $this[0] );
  1151. return reg.cy;
  1152. }
  1153. // bind to ready
  1154. else if( $$.is.fn(opts) ){
  1155. //debugger;
  1156. var ready = opts;
  1157. var domEle = $this[0];
  1158. var reg = $$.getRegistrationForInstance( domEle );
  1159. if( !reg ){
  1160. reg = $$.registerInstance( domEle );
  1161. }
  1162. if( reg && reg.cy && reg.cy.ready() ){
  1163. // already ready so just trigger now
  1164. reg.cy.trigger("ready", [], ready);
  1165. } else {
  1166. // not yet ready, so add to readies list
  1167. reg.readies.push( ready );
  1168. }
  1169. }
  1170. // proxy to create instance
  1171. else if( $$.is.plainObject(opts) ){
  1172. return $this.each(function(){
  1173. var options = $.extend({}, opts, {
  1174. container: $(this)[0]
  1175. });
  1176. cytoscape(options);
  1177. });
  1178. }
  1179. // proxy a function call
  1180. else {
  1181. var domEle = $this[0];
  1182. var rets = [];
  1183. var args = [];
  1184. for(var i = 1; i < arguments.length; i++){
  1185. args[i - 1] = arguments[i];
  1186. }
  1187. $this.each(function(){
  1188. var reg = $$.getRegistrationForInstance( domEle );
  1189. var cy = reg.cy;
  1190. var fnName = opts;
  1191. if( cy && $$.is.fn( cy[fnName] ) ){
  1192. var ret = cy[fnName].apply(cy, args);
  1193. rets.push(ret);
  1194. }
  1195. });
  1196. // if only one instance, don't need to return array
  1197. if( rets.length === 1 ){
  1198. rets = rets[0];
  1199. } else if( rets.length == 0 ){
  1200. rets = $(this);
  1201. }
  1202. return rets;
  1203. }
  1204. };
  1205. // allow access to the global cytoscape object under jquery for legacy reasons
  1206. $.cytoscape = cytoscape;
  1207. // use short alias (cy) if not already defined
  1208. if( $.fn.cy == null && $.cy == null ){
  1209. $.fn.cy = $.fn.cytoscape;
  1210. $.cy = $.cytoscape;
  1211. }
  1212. })(typeof jQuery !== 'undefined' ? jQuery : null , cytoscape);
  1213. ;(function($$){
  1214. // shamelessly taken from jQuery
  1215. // https://github.com/jquery/jquery/blob/master/src/event.js
  1216. $$.Event = function( src, props ) {
  1217. // Allow instantiation without the 'new' keyword
  1218. if ( !(this instanceof $$.Event) ) {
  1219. return new $$.Event( src, props );
  1220. }
  1221. // Event object
  1222. if ( src && src.type ) {
  1223. this.originalEvent = src;
  1224. this.type = src.type;
  1225. // Events bubbling up the document may have been marked as prevented
  1226. // by a handler lower down the tree; reflect the correct value.
  1227. this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
  1228. src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
  1229. // Event type
  1230. } else {
  1231. this.type = src;
  1232. }
  1233. // Put explicitly provided properties onto the event object
  1234. if ( props ) {
  1235. $$.util.extend( this, props );
  1236. }
  1237. // Create a timestamp if incoming event doesn't have one
  1238. this.timeStamp = src && src.timeStamp || +new Date;
  1239. // Mark it as fixed
  1240. //this[ jQuery.expando ] = true;
  1241. };
  1242. function returnFalse() {
  1243. return false;
  1244. }
  1245. function returnTrue() {
  1246. return true;
  1247. }
  1248. // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
  1249. // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
  1250. $$.Event.prototype = {
  1251. preventDefault: function() {
  1252. this.isDefaultPrevented = returnTrue;
  1253. var e = this.originalEvent;
  1254. if ( !e ) {
  1255. return;
  1256. }
  1257. // if preventDefault exists run it on the original event
  1258. if ( e.preventDefault ) {
  1259. e.preventDefault();
  1260. // otherwise set the returnValue property of the original event to false (IE)
  1261. } else {
  1262. e.returnValue = false;
  1263. }
  1264. },
  1265. stopPropagation: function() {
  1266. this.isPropagationStopped = returnTrue;
  1267. var e = this.originalEvent;
  1268. if ( !e ) {
  1269. return;
  1270. }
  1271. // if stopPropagation exists run it on the original event
  1272. if ( e.stopPropagation ) {
  1273. e.stopPropagation();
  1274. }
  1275. // otherwise set the cancelBubble property of the original event to true (IE)
  1276. e.cancelBubble = true;
  1277. },
  1278. stopImmediatePropagation: function() {
  1279. this.isImmediatePropagationStopped = returnTrue;
  1280. this.stopPropagation();
  1281. },
  1282. isDefaultPrevented: returnFalse,
  1283. isPropagationStopped: returnFalse,
  1284. isImmediatePropagationStopped: returnFalse
  1285. };
  1286. })( cytoscape );
  1287. ;(function($$){
  1288. // metaprogramming makes me happy
  1289. // use this module to cherry pick functions into your prototype
  1290. // (useful for functions shared between the core and collections, for example)
  1291. // e.g.
  1292. // $$.fn.collection({
  1293. // foo: $$.define.foo({ /* params... */ })
  1294. // });
  1295. $$.define = {
  1296. // access data field
  1297. data: function( params ){
  1298. var defaults = {
  1299. field: "data",
  1300. bindingEvent: "data",
  1301. allowBinding: false,
  1302. allowSetting: false,
  1303. allowGetting: false,
  1304. settingEvent: "data",
  1305. settingTriggersEvent: false,
  1306. triggerFnName: "trigger",
  1307. immutableKeys: {}, // key => true if immutable
  1308. updateMappers: false
  1309. };
  1310. params = $$.util.extend({}, defaults, params);
  1311. return function( name, value ){
  1312. var p = params;
  1313. var self = this;
  1314. var selfIsArrayLike = self.length !== undefined;
  1315. var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
  1316. var single = selfIsArrayLike ? self[0] : self;
  1317. // .data("foo", ...)
  1318. if( $$.is.string(name) ){ // set or get property
  1319. // .data("foo")
  1320. if( p.allowGetting && value === undefined ){ // get
  1321. var ret;
  1322. if( single ){
  1323. ret = single._private[ p.field ][ name ];
  1324. }
  1325. return ret;
  1326. // .data("foo", "bar")
  1327. } else if( p.allowSetting && value !== undefined ) { // set
  1328. var valid = !p.immutableKeys[name];
  1329. if( valid ){
  1330. for( var i = 0, l = all.length; i < l; i++ ){
  1331. all[i]._private[ p.field ][ name ] = value;
  1332. }
  1333. // update mappers if asked
  1334. if( p.updateMappers ){ self.updateMappers(); }
  1335. if( p.settingTriggersEvent ){
  1336. self[ p.triggerFnName ]( p.settingEvent );
  1337. }
  1338. }
  1339. }
  1340. // .data({ "foo": "bar" })
  1341. } else if( p.allowSetting && $$.is.plainObject(name) ){ // extend
  1342. var obj = name;
  1343. var k, v;
  1344. for( k in obj ){
  1345. v = obj[ k ];
  1346. var valid = !p.immutableKeys[k];
  1347. if( valid ){
  1348. for( var i = 0, l = all.length; i < l; i++ ){
  1349. all[i]._private[ p.field ][ k ] = v;
  1350. }
  1351. }
  1352. }
  1353. // update mappers if asked
  1354. if( p.updateMappers ){ self.updateMappers(); }
  1355. if( p.settingTriggersEvent ){
  1356. self[ p.triggerFnName ]( p.settingEvent );
  1357. }
  1358. // .data(function(){ ... })
  1359. } else if( p.allowBinding && $$.is.fn(name) ){ // bind to event
  1360. var fn = name;
  1361. self.bind( p.bindingEvent, fn );
  1362. // .data()
  1363. } else if( p.allowGetting && name === undefined ){ // get whole object
  1364. var ret;
  1365. if( single ){
  1366. ret = single._private[ p.field ];
  1367. }
  1368. return ret;
  1369. }
  1370. return self; // maintain chainability
  1371. }; // function
  1372. }, // data
  1373. batchData: function( params ){
  1374. var defaults = {
  1375. field: "data",
  1376. event: "data",
  1377. triggerFnName: "trigger",
  1378. immutableKeys: {}, // key => true if immutable
  1379. updateMappers: false
  1380. };
  1381. var p = params = $$.util.extend({}, defaults, params);
  1382. return function( map ){
  1383. var self = this;
  1384. var selfIsArrayLike = self.length !== undefined;
  1385. var eles = selfIsArrayLike ? self : self._private.elements;
  1386. if( eles.length === 0 ){ return self; }
  1387. var cy = selfIsArrayLike ? eles[0]._private.cy : self; // NB must have at least 1 ele to get cy
  1388. for( var i = 0; i < eles.length; i++ ){
  1389. var ele = eles[i];
  1390. var id = ele._private.data.id;
  1391. var mapData = map[id];
  1392. if( mapData !== undefined && mapData !== null ){
  1393. var obj = mapData;
  1394. var k, v;
  1395. // set the (k, v) pairs from the map
  1396. for( k in obj ){
  1397. v = obj[ k ];
  1398. var valid = !p.immutableKeys[k];
  1399. if( valid ){
  1400. ele._private[ p.field ][ k ] = v;
  1401. }
  1402. }
  1403. } // if
  1404. } // for
  1405. // update mappers if asked
  1406. var coln = new $$.Collection(cy, eles);
  1407. if( p.updateMappers ){ coln.updateMappers(); }
  1408. coln[ p.triggerFnName ]( p.event );
  1409. return self; // chaining
  1410. };
  1411. },
  1412. // remove data field
  1413. removeData: function( params ){
  1414. var defaults = {
  1415. field: "data",
  1416. event: "data",
  1417. triggerFnName: "trigger",
  1418. triggerEvent: false,
  1419. immutableKeys: {} // key => true if immutable
  1420. };
  1421. params = $$.util.extend({}, defaults, params);
  1422. return function( names ){
  1423. var p = params;
  1424. var self = this;
  1425. var selfIsArrayLike = self.length !== undefined;
  1426. var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
  1427. var single = selfIsArrayLike ? self[0] : self;
  1428. // .removeData("foo bar")
  1429. if( $$.is.string(names) ){ // then get the list of keys, and delete them
  1430. var keys = names.split(/\s+/);
  1431. var l = keys.length;
  1432. for( var i = 0; i < l; i++ ){ // delete each non-empty key
  1433. var key = keys[i];
  1434. if( $$.is.emptyString(key) ){ continue; }
  1435. var valid = !p.immutableKeys[ key ]; // not valid if immutable
  1436. if( valid ){
  1437. for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){
  1438. delete all[ i_a ]._private[ p.field ][ key ];
  1439. }
  1440. }
  1441. }
  1442. if( p.triggerEvent ){
  1443. self[ p.triggerFnName ]( p.event );
  1444. }
  1445. // .removeData()
  1446. } else if( names === undefined ){ // then delete all keys
  1447. for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){
  1448. var _privateFields = all[ i_a ]._private[ p.field ];
  1449. for( var key in _privateFields ){
  1450. var validKeyToDelete = !p.immutableKeys[ key ];
  1451. if( validKeyToDelete ){
  1452. delete _privateFields[ key ];
  1453. }
  1454. }
  1455. }
  1456. if( p.triggerEvent ){
  1457. self[ p.triggerFnName ]( p.event );
  1458. }
  1459. }
  1460. return self; // maintain chaining
  1461. }; // function
  1462. }, // removeData
  1463. // event function reusable stuff
  1464. event: {
  1465. regex: /(\w+)(\.\w+)?/, // regex for matching event strings (e.g. "click.namespace")
  1466. optionalTypeRegex: /(\w+)?(\.\w+)?/,
  1467. // properties to copy to the event obj
  1468. props: "altKey bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase metaKey offsetX offsetY originalTarget pageX pageY prevValue relatedTarget screenX screenY shiftKey target view which".split(/\s+/),
  1469. aliases: "mousedown mouseup click mouseover mouseout mousemove touchstart touchmove touchend grab drag free".split(/\s+/),
  1470. aliasesOn: function( thisPrototype ){
  1471. var aliases = $$.define.event.aliases;
  1472. for( var i = 0; i < aliases.length; i++ ){
  1473. var eventType = aliases[i];
  1474. thisPrototype[ eventType ] = function(data, callback){
  1475. if( $$.is.fn(callback) ){
  1476. this.on(eventType, data, callback);
  1477. } else if( $$.is.fn(data) ){
  1478. callback = data;
  1479. this.on(eventType, callback);
  1480. } else {
  1481. this.trigger(eventType);
  1482. }
  1483. return this; // maintain chaining
  1484. };
  1485. }
  1486. },
  1487. falseCallback: function(){ return false; }
  1488. },
  1489. // event binding
  1490. on: function( params ){
  1491. var defaults = {
  1492. unbindSelfOnTrigger: false,
  1493. unbindAllBindersOnTrigger: false
  1494. };
  1495. params = $$.util.extend({}, defaults, params);
  1496. return function(events, selector, data, callback){
  1497. var self = this;
  1498. var selfIsArrayLike = self.length !== undefined;
  1499. var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
  1500. var single = selfIsArrayLike ? self[0] : self;
  1501. var eventsIsString = $$.is.string(events);
  1502. var p = params;
  1503. if( $$.is.plainObject(selector) ){ // selector is actually data
  1504. callback = data;
  1505. data = selector;
  1506. selector = undefined;
  1507. } else if( $$.is.fn(selector) || selector === false ){ // selector is actually callback
  1508. callback = selector;
  1509. data = undefined;
  1510. selector = undefined;
  1511. }
  1512. if( $$.is.fn(data) || data === false ){ // data is actually callback
  1513. callback = data;
  1514. data = undefined;
  1515. }
  1516. // if there isn't a callback, we can't really do anything
  1517. // (can't speak for mapped events arg version)
  1518. if( !($$.is.fn(callback) || callback === false) && eventsIsString ){
  1519. return self; // maintain chaining
  1520. }
  1521. if( eventsIsString ){ // then convert to map
  1522. var map = {};
  1523. map[ events ] = callback;
  1524. events = map;
  1525. }
  1526. for( var evts in events ){
  1527. callback = events[evts];
  1528. if( callback === false ){
  1529. callback = $$.define.event.falseCallback;
  1530. }
  1531. if( !$$.is.fn(callback) ){ continue; }
  1532. evts = evts.split(/\s+/);
  1533. for( var i = 0; i < evts.length; i++ ){
  1534. var evt = evts[i];
  1535. if( $$.is.emptyString(evt) ){ continue; }
  1536. var match = evt.match( $$.define.event.regex ); // type[.namespace]
  1537. if( match ){
  1538. var type = match[1];
  1539. var namespace = match[2] ? match[2] : undefined;
  1540. var listener = {
  1541. callback: callback, // callback to run
  1542. data: data, // extra data in eventObj.data
  1543. delegated: selector ? true : false, // whether the evt is delegated
  1544. selector: selector, // the selector to match for delegated events
  1545. type: type, // the event type (e.g. "click")
  1546. namespace: namespace, // the event namespace (e.g. ".foo")
  1547. unbindSelfOnTrigger: p.unbindSelfOnTrigger,
  1548. unbindAllBindersOnTrigger: p.unbindAllBindersOnTrigger,
  1549. binders: all // who bound together
  1550. };
  1551. for( var j = 0; j < all.length; j++ ){
  1552. all[j]._private.listeners.push( listener );
  1553. }
  1554. }
  1555. } // for events array
  1556. } // for events map
  1557. return self; // maintain chaining
  1558. }; // function
  1559. }, // on
  1560. off: function( params ){
  1561. var defaults = {
  1562. };
  1563. params = $$.util.extend({}, defaults, params);
  1564. return function(events, selector, callback){
  1565. var self = this;
  1566. var selfIsArrayLike = self.length !== undefined;
  1567. var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
  1568. var single = selfIsArrayLike ? self[0] : self;
  1569. var eventsIsString = $$.is.string(events);
  1570. var p = params;
  1571. if( arguments.length === 0 ){ // then unbind all
  1572. for( var i = 0; i < all.length; i++ ){
  1573. all[i]._private.listeners = [];
  1574. }
  1575. return self; // maintain chaining
  1576. }
  1577. if( $$.is.fn(selector) || selector === false ){ // selector is actually callback
  1578. callback = selector;
  1579. selector = undefined;
  1580. }
  1581. if( eventsIsString ){ // then convert to map
  1582. var map = {};
  1583. map[ events ] = callback;
  1584. events = map;
  1585. }
  1586. for( var evts in events ){
  1587. callback = events[evts];
  1588. if( callback === false ){
  1589. callback = $$.define.event.falseCallback;
  1590. }
  1591. evts = evts.split(/\s+/);
  1592. for( var h = 0; h < evts.length; h++ ){
  1593. var evt = evts[h];
  1594. if( $$.is.emptyString(evt) ){ continue; }
  1595. var match = evt.match( $$.define.event.optionalTypeRegex ); // [type][.namespace]
  1596. if( match ){
  1597. var type = match[1] ? match[1] : undefined;
  1598. var namespace = match[2] ? match[2] : undefined;
  1599. for( var i = 0; i < all.length; i++ ){ //
  1600. var listeners = all[i]._private.listeners;
  1601. for( var j = 0; j < listeners.length; j++ ){
  1602. var listener = listeners[j];
  1603. var nsMatches = !namespace || namespace === listener.namespace;
  1604. var typeMatches = !type || listener.type === type;
  1605. var cbMatches = !callback || callback === listener.callback;
  1606. var listenerMatches = nsMatches && typeMatches && cbMatches;
  1607. // delete listener if it matches
  1608. if( listenerMatches ){
  1609. listeners.splice(j, 1);
  1610. j--;
  1611. }
  1612. } // for listeners
  1613. } // for all
  1614. } // if match
  1615. } // for events array
  1616. } // for events map
  1617. return self; // maintain chaining
  1618. }; // function
  1619. }, // off
  1620. trigger: function( params ){
  1621. var defaults = {};
  1622. params = $$.util.extend({}, defaults, params);
  1623. return function(events, extraParams, fnToTrigger){
  1624. var self = this;
  1625. var selfIsArrayLike = self.length !== undefined;
  1626. var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
  1627. var single = selfIsArrayLike ? self[0] : self;
  1628. var eventsIsString = $$.is.string(events);
  1629. var eventsIsObject = $$.is.plainObject(events);
  1630. var eventsIsEvent = $$.is.event(events);
  1631. var p = params;
  1632. var cy = this._private.cy || this;
  1633. if( eventsIsString ){ // then make a plain event object for each event name
  1634. var evts = events.split(/\s+/);
  1635. events = [];
  1636. for( var i = 0; i < evts.length; i++ ){
  1637. var evt = evts[i];
  1638. if( $$.is.emptyString(evt) ){ continue; }
  1639. var match = evt.match( $$.define.event.regex ); // type[.namespace]
  1640. var type = match[1];
  1641. var namespace = match[2] ? match[2] : undefined;
  1642. events.push( {
  1643. type: type,
  1644. namespace: namespace
  1645. } );
  1646. }
  1647. } else if( eventsIsObject ){ // put in length 1 array
  1648. var eventArgObj = events;
  1649. events = [ eventArgObj ];
  1650. }
  1651. if( extraParams ){
  1652. if( !$$.is.array(extraParams) ){ // make sure extra params are in an array if specified
  1653. extraParams = [ extraParams ];
  1654. }
  1655. } else { // otherwise, we've got nothing
  1656. extraParams = [];
  1657. }
  1658. for( var i = 0; i < events.length; i++ ){ // trigger each event in order
  1659. var evtObj = events[i];
  1660. for( var j = 0; j < all.length; j++ ){ // for each
  1661. var triggerer = all[j];
  1662. var listeners = triggerer._private.listeners;
  1663. var triggererIsElement = $$.is.element(triggerer);
  1664. var bubbleUp = triggererIsElement;
  1665. // create the event for this element from the event object
  1666. var evt;
  1667. if( eventsIsEvent ){ // then just get the object
  1668. evt = evtObj;
  1669. evt.cyTarget = evt.cyTarget || triggerer;
  1670. evt.cy = evt.cy || cy;
  1671. evt.namespace = evt.namespace || evtObj.namespace;
  1672. } else { // then we have to make one
  1673. evt = new $$.Event( evtObj, {
  1674. cyTarget: triggerer,
  1675. cy: cy,
  1676. namespace: evtObj.namespace
  1677. } );
  1678. // copy properties like jQuery does
  1679. var props = $$.define.event.props;
  1680. for( var k = 0; k < props.length; k++ ){
  1681. var prop = props[k];
  1682. evt[ prop ] = evtObj[ prop ];
  1683. }
  1684. }
  1685. if( fnToTrigger ){ // then override the listeners list with just the one we specified
  1686. listeners = [{
  1687. namespace: evt.namespace,
  1688. type: evt.type,
  1689. callback: fnToTrigger
  1690. }];
  1691. }
  1692. for( var k = 0; k < listeners.length; k++ ){ // check each listener
  1693. var lis = listeners[k];
  1694. var nsMatches = !lis.namespace || lis.namespace === evt.namespace;
  1695. var typeMatches = lis.type === evt.type;
  1696. var targetMatches = lis.delegated ? ( triggerer !== evt.cyTarget && $$.is.element(evt.cyTarget) && evt.cyTarget.is(lis.selector) ) : (true); // we're not going to validate the hierarchy; that's too expensive
  1697. var listenerMatches = nsMatches && typeMatches && targetMatches;
  1698. if( listenerMatches ){ // then trigger it
  1699. var args = [ evt ];
  1700. args = args.concat( extraParams ); // add extra params to args list
  1701. if( lis.data ){ // add on data plugged into binding
  1702. evt.data = lis.data;
  1703. } else { // or clear it in case the event obj is reused
  1704. evt.data = undefined;
  1705. }
  1706. if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener
  1707. listeners.splice(k, 1);
  1708. k--;
  1709. }
  1710. if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders
  1711. var binders = lis.binders;
  1712. for( var l = 0; l < binders.length; l++ ){
  1713. var binder = binders[l];
  1714. if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it
  1715. var binderListeners = binder._private.listeners;
  1716. for( var m = 0; m < binderListeners.length; m++ ){
  1717. var binderListener = binderListeners[m];
  1718. if( binderListener === lis ){ // delete listener from list
  1719. binderListeners.splice(m, 1);
  1720. m--;
  1721. }
  1722. }
  1723. }
  1724. }
  1725. // run the callback
  1726. var context = lis.delegated ? evt.cyTarget : triggerer;
  1727. var ret = lis.callback.apply( context, args );
  1728. if( ret === false || evt.isPropagationStopped() ){
  1729. // then don't bubble
  1730. bubbleUp = false;
  1731. if( ret === false ){
  1732. // returning false is a shorthand for stopping propagation and preventing the def. action
  1733. evt.stopPropagation();
  1734. evt.preventDefault();
  1735. }
  1736. }
  1737. } // if listener matches
  1738. } // for each listener
  1739. // bubble up event for elements
  1740. if( bubbleUp ){
  1741. var parent = triggerer.parent();
  1742. var hasParent = parent.length !== 0;
  1743. if( hasParent ){ // then bubble up to parent
  1744. parent = parent[0];
  1745. parent.trigger(evt);
  1746. } else { // otherwise, bubble up to the core
  1747. cy.trigger(evt);
  1748. }
  1749. }
  1750. } // for each of all
  1751. } // for each event
  1752. return self; // maintain chaining
  1753. }; // function
  1754. } // trigger
  1755. }; // define
  1756. })( cytoscape );
  1757. ;(function($$, window){
  1758. var isTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
  1759. $$.Style = function( cy ){
  1760. if( !(this instanceof $$.Style) ){
  1761. return new $$.Style(cy);
  1762. }
  1763. if( !$$.is.core(cy) ){
  1764. $$.util.error("A style must have a core reference");
  1765. return;
  1766. }
  1767. this._private = {
  1768. cy: cy,
  1769. coreStyle: {}
  1770. };
  1771. this.length = 0;
  1772. this.addDefaultStylesheet();
  1773. };
  1774. // nice-to-have aliases
  1775. $$.style = $$.Style;
  1776. $$.styfn = $$.Style.prototype;
  1777. // define functions in the Style prototype
  1778. $$.fn.style = function( fnMap, options ){
  1779. for( var fnName in fnMap ){
  1780. var fn = fnMap[ fnName ];
  1781. $$.Style.prototype = fn;
  1782. }
  1783. };
  1784. // a dummy stylesheet object that doesn't need a reference to the core
  1785. $$.stylesheet = $$.Stylesheet = function(){
  1786. if( !(this instanceof $$.Stylesheet) ){
  1787. return new $$.Stylesheet();
  1788. }
  1789. this.length = 0;
  1790. };
  1791. // just store the selector to be parsed later
  1792. $$.Stylesheet.prototype.selector = function( selector ){
  1793. var i = this.length++;
  1794. this[i] = {
  1795. selector: selector,
  1796. properties: []
  1797. };
  1798. return this; // chaining
  1799. };
  1800. // just store the property to be parsed later
  1801. $$.Stylesheet.prototype.css = function( name, value ){
  1802. var i = this.length - 1;
  1803. if( $$.is.string(name) ){
  1804. this[i].properties.push({
  1805. name: name,
  1806. value: value
  1807. });
  1808. } else if( $$.is.plainObject(name) ){
  1809. map = name;
  1810. for( var j = 0; j < $$.style.properties.length; j++ ){
  1811. var prop = $$.style.properties[j];
  1812. var mapVal = map[ prop.name ];
  1813. if( mapVal === undefined ){ // also try camel case name
  1814. mapVal = map[ $$.util.dash2camel(prop.name) ];
  1815. }
  1816. if( mapVal !== undefined ){
  1817. var name = prop.name;
  1818. var value = mapVal;
  1819. this[i].properties.push({
  1820. name: name,
  1821. value: value
  1822. });
  1823. }
  1824. }
  1825. }
  1826. return this; // chaining
  1827. };
  1828. // static function
  1829. $$.style.fromJson = function( cy, json ){
  1830. var style = new $$.Style(cy);
  1831. for( var i = 0; i < json.length; i++ ){
  1832. var context = json[i];
  1833. var selector = context.selector;
  1834. var props = context.css;
  1835. style.selector(selector); // apply selector
  1836. for( var name in props ){
  1837. var value = props[name];
  1838. style.css( name, value ); // apply property
  1839. }
  1840. }
  1841. return style;
  1842. };
  1843. $$.styfn.fromJson = function( json ){
  1844. var style = this;
  1845. style.resetToDefault();
  1846. for( var i = 0; i < json.length; i++ ){
  1847. var context = json[i];
  1848. var selector = context.selector;
  1849. var props = context.css;
  1850. style.selector(selector); // apply selector
  1851. for( var name in props ){
  1852. var value = props[name];
  1853. style.css( name, value ); // apply property
  1854. }
  1855. }
  1856. return style;
  1857. };
  1858. // generate a real style object from the dummy stylesheet
  1859. $$.Stylesheet.prototype.generateStyle = function( cy ){
  1860. var style = new $$.Style(cy);
  1861. for( var i = 0; i < this.length; i++ ){
  1862. var context = this[i];
  1863. var selector = context.selector;
  1864. var props = context.properties;
  1865. style.selector(selector); // apply selector
  1866. for( var j = 0; j < props.length; j++ ){
  1867. var prop = props[j];
  1868. style.css( prop.name, prop.value ); // apply property
  1869. }
  1870. }
  1871. return style;
  1872. };
  1873. $$.Stylesheet.prototype.assignToStyle = function( style, addDefaultStylesheet ){
  1874. style.clear();
  1875. if( addDefaultStylesheet || addDefaultStylesheet === undefined ){
  1876. style.addDefaultStylesheet();
  1877. }
  1878. for( var i = 0; i < this.length; i++ ){
  1879. var context = this[i];
  1880. var selector = context.selector;
  1881. var props = context.properties;
  1882. style.selector(selector); // apply selector
  1883. for( var j = 0; j < props.length; j++ ){
  1884. var prop = props[j];
  1885. style.css( prop.name, prop.value ); // apply property
  1886. }
  1887. }
  1888. };
  1889. (function(){
  1890. var number = $$.util.regex.number;
  1891. var rgba = $$.util.regex.rgbaNoBackRefs;
  1892. var hsla = $$.util.regex.hslaNoBackRefs;
  1893. var hex3 = $$.util.regex.hex3;
  1894. var hex6 = $$.util.regex.hex6;
  1895. // each visual style property has a type and needs to be validated according to it
  1896. $$.style.types = {
  1897. zeroOneNumber: { number: true, min: 0, max: 1, unitless: true },
  1898. nonNegativeInt: { number: true, min: 0, integer: true, unitless: true },
  1899. size: { number: true, min: 0, enums: ["auto"] },
  1900. bgSize: { number: true, min: 0, allowPercent: true },
  1901. color: { color: true },
  1902. lineStyle: { enums: ["solid", "dotted", "dashed"] },
  1903. curveStyle: { enums: ["bundled", "bezier"] },
  1904. fontFamily: { regex: "^([\\w- ]+(?:\\s*,\\s*[\\w- ]+)*)$" },
  1905. fontVariant: { enums: ["small-caps", "normal"] },
  1906. fontStyle: { enums: ["italic", "normal", "oblique"] },
  1907. fontWeight: { enums: ["normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "800", "900", 100, 200, 300, 400, 500, 600, 700, 800, 900] },
  1908. textDecoration: { enums: ["none", "underline", "overline", "line-through"] },
  1909. textTransform: { enums: ["none", "capitalize", "uppercase", "lowercase"] },
  1910. nodeShape: { enums: ["rectangle", "roundrectangle", "ellipse", "triangle",
  1911. "square", "pentagon", "hexagon", "heptagon", "octagon"] },
  1912. arrowShape: { enums: ["tee", "triangle", "square", "circle", "diamond", "none"] },
  1913. visibility: { enums: ["hidden", "visible"] },
  1914. valign: { enums: ["top", "center", "bottom"] },
  1915. halign: { enums: ["left", "center", "right"] },
  1916. positionx: { enums: ["left", "center", "right"], number: true, allowPercent: true },
  1917. positiony: { enums: ["top", "center", "bottom"], number: true, allowPercent: true },
  1918. bgRepeat: { enums: ["repeat", "repeat-x", "repeat-y", "no-repeat"] },
  1919. cursor: { enums: ["auto", "crosshair", "default", "e-resize", "n-resize", "ne-resize", "nw-resize", "pointer", "progress", "s-resize", "sw-resize", "text", "w-resize", "wait", "grab", "grabbing"] },
  1920. text: { string: true },
  1921. data: { mapping: true, regex: "^data\\s*\\(\\s*([\\w\\.]+)\\s*\\)$" },
  1922. mapData: { mapping: true, regex: "^mapData\\(([\\w\\.]+)\\s*\\,\\s*(" + number + ")\\s*\\,\\s*(" + number + ")\\s*,\\s*(" + number + "|\\w+|" + rgba + "|" + hsla + "|" + hex3 + "|" + hex6 + ")\\s*\\,\\s*(" + number + "|\\w+|" + rgba + "|" + hsla + "|" + hex3 + "|" + hex6 + ")\\)$" },
  1923. url: { regex: "^url\\s*\\(\\s*([^\\s]+)\\s*\\s*\\)|none|(.+)$" }
  1924. };
  1925. // define visual style properties
  1926. var t = $$.style.types;
  1927. $$.style.properties = [
  1928. // these are for elements
  1929. { name: "cursor", type: t.cursor },
  1930. { name: "text-valign", type: t.valign },
  1931. { name: "text-halign", type: t.halign },
  1932. { name: "color", type: t.color },
  1933. { name: "content", type: t.text },
  1934. { name: "text-outline-color", type: t.color },
  1935. { name: "text-outline-width", type: t.size },
  1936. { name: "text-outline-opacity", type: t.zeroOneNumber },
  1937. { name: "text-opacity", type: t.zeroOneNumber },
  1938. { name: "text-decoration", type: t.textDecoration },
  1939. { name: "text-transform", type: t.textTransform },
  1940. { name: "font-family", type: t.fontFamily },
  1941. { name: "font-style", type: t.fontStyle },
  1942. { name: "font-variant", type: t.fontVariant },
  1943. { name: "font-weight", type: t.fontWeight },
  1944. { name: "font-size", type: t.size },
  1945. { name: "min-zoomed-font-size", type: t.size },
  1946. { name: "visibility", type: t.visibility },
  1947. { name: "opacity", type: t.zeroOneNumber },
  1948. { name: "z-index", type: t.nonNegativeInt },
  1949. { name: "overlay-padding", type: t.size },
  1950. { name: "overlay-color", type: t.color },
  1951. { name: "overlay-opacity", type: t.zeroOneNumber },
  1952. // these are just for nodes
  1953. { name: "background-color", type: t.color },
  1954. { name: "background-opacity", type: t.zeroOneNumber },
  1955. { name: "background-image", type: t.url },
  1956. { name: "background-position-x", type: t.positionx },
  1957. { name: "background-position-y", type: t.positiony },
  1958. { name: "background-repeat", type: t.bgRepeat },
  1959. { name: "background-size-x", type: t.bgSize },
  1960. { name: "background-size-y", type: t.bgSize },
  1961. { name: "border-color", type: t.color },
  1962. { name: "border-opacity", type: t.zeroOneNumber },
  1963. { name: "border-width", type: t.size },
  1964. { name: "border-style", type: t.lineStyle },
  1965. { name: "height", type: t.size },
  1966. { name: "width", type: t.size },
  1967. { name: "padding-left", type: t.size },
  1968. { name: "padding-right", type: t.size },
  1969. { name: "padding-top", type: t.size },
  1970. { name: "padding-bottom", type: t.size },
  1971. { name: "shape", type: t.nodeShape },
  1972. // these are just for edges
  1973. { name: "source-arrow-shape", type: t.arrowShape },
  1974. { name: "target-arrow-shape", type: t.arrowShape },
  1975. { name: "source-arrow-color", type: t.color },
  1976. { name: "target-arrow-color", type: t.color },
  1977. { name: "line-style", type: t.lineStyle },
  1978. { name: "line-color", type: t.color },
  1979. { name: "control-point-step-size", type: t.size },
  1980. { name: "curve-style", type: t.curveStyle },
  1981. // these are just for the core
  1982. { name: "selection-box-color", type: t.color },
  1983. { name: "selection-box-opacity", type: t.zeroOneNumber },
  1984. { name: "selection-box-border-color", type: t.color },
  1985. { name: "selection-box-border-width", type: t.size },
  1986. { name: "panning-cursor", type: t.cursor },
  1987. { name: "active-bg-color", type: t.color },
  1988. { name: "active-bg-opacity", type: t.zeroOneNumber },
  1989. { name: "active-bg-size", type: t.size }
  1990. ];
  1991. // allow access of properties by name ( e.g. $$.style.properties.height )
  1992. var props = $$.style.properties;
  1993. for( var i = 0; i < props.length; i++ ){
  1994. var prop = props[i];
  1995. props[ prop.name ] = prop; // allow lookup by name
  1996. }
  1997. })();
  1998. // adds the default stylesheet to the current style
  1999. $$.styfn.addDefaultStylesheet = function(){
  2000. // to be nice, we build font related style properties from the core container
  2001. // so that cytoscape matches the style of its container by default
  2002. var fontFamily = this.containerPropertyAsString("font-family") || "sans-serif";
  2003. var fontStyle = this.containerPropertyAsString("font-style") || "normal";
  2004. var fontVariant = this.containerPropertyAsString("font-variant") || "normal";
  2005. var fontWeight = this.containerPropertyAsString("font-weight") || "normal";
  2006. var color = this.containerPropertyAsString("color") || "#000";
  2007. var textTransform = this.containerPropertyAsString("text-transform") || "none";
  2008. var textDecoration = this.containerPropertyAsString("text-decoration") || "none";
  2009. var fontSize = this.containerPropertyAsString("font-size") || 12;
  2010. // fill the style with the default stylesheet
  2011. this
  2012. .selector("node, edge") // common properties
  2013. .css({
  2014. "cursor": "default",
  2015. "text-valign": "top",
  2016. "text-halign": "center",
  2017. "color": color,
  2018. "content": undefined, // => no label
  2019. "text-outline-color": "#000",
  2020. "text-outline-width": 0,
  2021. "text-outline-opacity": 1,
  2022. "text-opacity": 1,
  2023. "text-decoration": "none",
  2024. "text-transform": textTransform,
  2025. "font-family": fontFamily,
  2026. "font-style": fontStyle,
  2027. "font-variant": fontVariant,
  2028. "font-weight": fontWeight,
  2029. "font-size": fontSize,
  2030. "min-zoomed-font-size": 0,
  2031. "visibility": "visible",
  2032. "opacity": 1,
  2033. "z-index": 0,
  2034. "content": "",
  2035. "overlay-opacity": 0,
  2036. "overlay-color": "#000",
  2037. "overlay-padding": 10,
  2038. // node props
  2039. "background-color": "#888",
  2040. "background-opacity": 1,
  2041. "background-image": "none",
  2042. "border-color": "#000",
  2043. "border-opacity": 1,
  2044. "border-width": 0,
  2045. "border-style": "solid",
  2046. "height": 30,
  2047. "width": 30,
  2048. "padding-top": 0,
  2049. "padding-bottom": 0,
  2050. "padding-left": 0,
  2051. "padding-right": 0,
  2052. "shape": "ellipse",
  2053. // edge props
  2054. "source-arrow-shape": "none",
  2055. "target-arrow-shape": "none",
  2056. "source-arrow-color": "#bbb",
  2057. "target-arrow-color": "#bbb",
  2058. "line-style": "solid",
  2059. "line-color": "#bbb",
  2060. "control-point-step-size": 40,
  2061. "curve-style": "bezier"
  2062. })
  2063. .selector("$node > node") // compound (parent) node properties
  2064. .css({
  2065. "width": "auto",
  2066. "height": "auto",
  2067. "shape": "rectangle",
  2068. "background-opacity": 0.5,
  2069. "padding-top": 10,
  2070. "padding-right": 10,
  2071. "padding-left": 10,
  2072. "padding-bottom": 10
  2073. })
  2074. .selector("edge") // just edge properties
  2075. .css({
  2076. "width": 1,
  2077. })
  2078. .selector(":active")
  2079. .css({
  2080. "overlay-color": "black",
  2081. "overlay-padding": 10,
  2082. "overlay-opacity": 0.25
  2083. })
  2084. .selector("core") // just core properties
  2085. .css({
  2086. "selection-box-color": "#ddd",
  2087. "selection-box-opacity": 0.65,
  2088. "selection-box-border-color": "#aaa",
  2089. "selection-box-border-width": 1,
  2090. "panning-cursor": "grabbing",
  2091. "active-bg-color": "black",
  2092. "active-bg-opacity": 0.15,
  2093. "active-bg-size": isTouch ? 40 : 15
  2094. })
  2095. ;
  2096. };
  2097. // remove all contexts
  2098. $$.styfn.clear = function(){
  2099. this._private.newStyle = true;
  2100. for( var i = 0; i < this.length; i++ ){
  2101. delete this[i];
  2102. }
  2103. this.length = 0;
  2104. return this; // chaining
  2105. };
  2106. $$.styfn.resetToDefault = function(){
  2107. this.clear();
  2108. this.addDefaultStylesheet();
  2109. return this;
  2110. };
  2111. // builds a style object for the "core" selector
  2112. $$.styfn.core = function(){
  2113. return this._private.coreStyle;
  2114. };
  2115. // parse a property; return null on invalid; return parsed property otherwise
  2116. // fields :
  2117. // - name : the name of the property
  2118. // - value : the parsed, native-typed value of the property
  2119. // - strValue : a string value that represents the property value in valid css
  2120. // - bypass : true iff the property is a bypass property
  2121. $$.styfn.parse = function( name, value, propIsBypass ){
  2122. name = $$.util.camel2dash( name ); // make sure the property name is in dash form (e.g. "property-name" not "propertyName")
  2123. var property = $$.style.properties[ name ];
  2124. var passedValue = value;
  2125. if( !property ){ return null; } // return null on property of unknown name
  2126. if( value === undefined || value === null ){ return null; } // can't assign null
  2127. var valueIsString = $$.is.string(value);
  2128. if( valueIsString ){ // trim the value to make parsing easier
  2129. value = $$.util.trim( value );
  2130. }
  2131. var type = property.type;
  2132. if( !type ){ return null; } // no type, no luck
  2133. // check if bypass is null or empty string (i.e. indication to delete bypass property)
  2134. if( propIsBypass && (value === "" || value === null) ){
  2135. return {
  2136. name: name,
  2137. value: value,
  2138. bypass: true,
  2139. deleteBypass: true
  2140. };
  2141. }
  2142. // check if value is mapped
  2143. var data, mapData;
  2144. if( !valueIsString ){
  2145. // then don't bother to do the expensive regex checks
  2146. } else if( data = new RegExp( $$.style.types.data.regex ).exec( value ) ){
  2147. return {
  2148. name: name,
  2149. value: data,
  2150. strValue: value,
  2151. mapped: $$.style.types.data,
  2152. field: data[1],
  2153. bypass: propIsBypass
  2154. };
  2155. } else if( mapData = new RegExp( $$.style.types.mapData.regex ).exec( value ) ){
  2156. // we can map only if the type is a colour or a number
  2157. if( !(type.color || type.number) ){ return false; }
  2158. var valueMin = this.parse( name, mapData[4]); // parse to validate
  2159. if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped
  2160. var valueMax = this.parse( name, mapData[5]); // parse to validate
  2161. if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped
  2162. // check if valueMin and valueMax are the same
  2163. if( valueMin.value === valueMax.value ){
  2164. return false; // can't make much of a mapper without a range
  2165. } else if( type.color ){
  2166. var c1 = valueMin.value;
  2167. var c2 = valueMax.value;
  2168. var same = c1[0] === c2[0] // red
  2169. && c1[1] === c2[1] // green
  2170. && c1[2] === c2[2] // blue
  2171. && ( // optional alpha
  2172. c1[3] === c2[3] // same alpha outright
  2173. || (
  2174. (c1[3] == null || c1[3] === 1) // full opacity for colour 1?
  2175. &&
  2176. (c2[3] == null || c2[3] === 1) // full opacity for colour 2?
  2177. )
  2178. )
  2179. ;
  2180. if( same ){ return false; } // can't make a mapper without a range
  2181. }
  2182. return {
  2183. name: name,
  2184. value: mapData,
  2185. strValue: value,
  2186. mapped: $$.style.types.mapData,
  2187. field: mapData[1],
  2188. fieldMin: parseFloat( mapData[2] ), // min & max are numeric
  2189. fieldMax: parseFloat( mapData[3] ),
  2190. valueMin: valueMin.value,
  2191. valueMax: valueMax.value,
  2192. bypass: propIsBypass
  2193. };
  2194. }
  2195. // TODO check if value is inherited (i.e. "inherit")
  2196. // check the type and return the appropriate object
  2197. if( type.number ){
  2198. var units;
  2199. if( !type.unitless ){
  2200. if( valueIsString ){
  2201. var match = value.match( "^(" + $$.util.regex.number + ")(px|em" + (type.allowPercent ? "|\\%" : "") + ")?" + "$" );
  2202. if( !type.enums ){
  2203. if( !match ){ return null; } // no match => not a number
  2204. value = match[1];
  2205. units = match[2] || "px";
  2206. }
  2207. } else {
  2208. units = "px"; // implicitly px if unspecified
  2209. }
  2210. }
  2211. value = parseFloat( value );
  2212. // check if this number type also accepts special keywords in place of numbers
  2213. // (i.e. `left`, `auto`, etc)
  2214. if( isNaN(value) && type.enums !== undefined ){
  2215. value = passedValue;
  2216. for( var i = 0; i < type.enums.length; i++ ){
  2217. var en = type.enums[i];
  2218. if( en === value ){
  2219. return {
  2220. name: name,
  2221. value: value,
  2222. strValue: value,
  2223. bypass: propIsBypass
  2224. };
  2225. }
  2226. }
  2227. return null; // failed on enum after failing on number
  2228. }
  2229. // check if value must be an integer
  2230. if( type.integer && !$$.is.integer(value) ){
  2231. return null;
  2232. }
  2233. // check value is within range
  2234. if( (type.min !== undefined && value < type.min)
  2235. || (type.max !== undefined && value > type.max)
  2236. ){
  2237. return null;
  2238. }
  2239. var ret = {
  2240. name: name,
  2241. value: value,
  2242. strValue: "" + value + (units ? units : ""),
  2243. units: units,
  2244. bypass: propIsBypass,
  2245. pxValue: type.unitless || units === "%" ?
  2246. undefined
  2247. :
  2248. ( units === "px" || !units ? (value) : (this.getEmSizeInPixels() * value) )
  2249. };
  2250. return ret;
  2251. } else if( type.color ){
  2252. var tuple = $$.util.color2tuple( value );
  2253. return {
  2254. name: name,
  2255. value: tuple,
  2256. strValue: value,
  2257. bypass: propIsBypass
  2258. };
  2259. } else if( type.enums ){
  2260. for( var i = 0; i < type.enums.length; i++ ){
  2261. var en = type.enums[i];
  2262. if( en === value ){
  2263. return {
  2264. name: name,
  2265. value: value,
  2266. strValue: value,
  2267. bypass: propIsBypass
  2268. };
  2269. }
  2270. }
  2271. } else if( type.regex ){
  2272. var regex = new RegExp( type.regex ); // make a regex from the type
  2273. var m = regex.exec( value );
  2274. if( m ){ // regex matches
  2275. return {
  2276. name: name,
  2277. value: m,
  2278. strValue: value,
  2279. bypass: propIsBypass
  2280. };
  2281. } else { // regex doesn't match
  2282. return null; // didn't match the regex so the value is bogus
  2283. }
  2284. } else if( type.string ){
  2285. // just return
  2286. return {
  2287. name: name,
  2288. value: value,
  2289. strValue: value,
  2290. bypass: propIsBypass
  2291. };
  2292. } else {
  2293. return null; // not a type we can handle
  2294. }
  2295. };
  2296. // gets what an em size corresponds to in pixels relative to a dom element
  2297. $$.styfn.getEmSizeInPixels = function(){
  2298. var cy = this._private.cy;
  2299. var domElement = cy.container();
  2300. if( window && domElement ){
  2301. var pxAsStr = window.getComputedStyle(domElement).getPropertyValue("font-size");
  2302. var px = parseFloat( pxAsStr );
  2303. return px;
  2304. } else {
  2305. return 1; // in case we're running outside of the browser
  2306. }
  2307. };
  2308. // gets css property from the core container
  2309. $$.styfn.containerCss = function( propName ){
  2310. var cy = this._private.cy;
  2311. var domElement = cy.container();
  2312. if( window && domElement ){
  2313. return window.getComputedStyle(domElement).getPropertyValue( propName );
  2314. }
  2315. };
  2316. $$.styfn.containerProperty = function( propName ){
  2317. var propStr = this.containerCss( propName );
  2318. var prop = this.parse( propName, propStr );
  2319. return prop;
  2320. };
  2321. $$.styfn.containerPropertyAsString = function( propName ){
  2322. var prop = this.containerProperty( propName );
  2323. if( prop ){
  2324. return prop.strValue;
  2325. }
  2326. };
  2327. // create a new context from the specified selector string and switch to that context
  2328. $$.styfn.selector = function( selectorStr ){
  2329. // "core" is a special case and does not need a selector
  2330. var selector = selectorStr === "core" ? null : new $$.Selector( selectorStr );
  2331. var i = this.length++; // new context means new index
  2332. this[i] = {
  2333. selector: selector,
  2334. properties: []
  2335. };
  2336. return this; // chaining
  2337. };
  2338. // add one or many css rules to the current context
  2339. $$.styfn.css = function(){
  2340. var args = arguments;
  2341. switch( args.length ){
  2342. case 1:
  2343. var map = args[0];
  2344. for( var i = 0; i < $$.style.properties.length; i++ ){
  2345. var prop = $$.style.properties[i];
  2346. var mapVal = map[ prop.name ];
  2347. if( mapVal === undefined ){
  2348. mapVal = map[ $$.util.dash2camel(prop.name) ];
  2349. }
  2350. if( mapVal !== undefined ){
  2351. this.cssRule( prop.name, mapVal );
  2352. }
  2353. }
  2354. break;
  2355. case 2:
  2356. this.cssRule( args[0], args[1] );
  2357. break;
  2358. default:
  2359. break; // do nothing if args are invalid
  2360. }
  2361. return this; // chaining
  2362. };
  2363. // add a single css rule to the current context
  2364. $$.styfn.cssRule = function( name, value ){
  2365. // name-value pair
  2366. var property = this.parse( name, value );
  2367. // add property to current context if valid
  2368. if( property ){
  2369. var i = this.length - 1;
  2370. this[i].properties.push( property );
  2371. // add to core style if necessary
  2372. var currentSelectorIsCore = !this[i].selector;
  2373. if( currentSelectorIsCore ){
  2374. this._private.coreStyle[ property.name ] = property;
  2375. }
  2376. }
  2377. return this; // chaining
  2378. };
  2379. // apply a property to the style (for internal use)
  2380. // returns whether application was successful
  2381. //
  2382. // now, this function flattens the property, and here's how:
  2383. //
  2384. // for parsedProp:{ bypass: true, deleteBypass: true }
  2385. // no property is generated, instead the bypass property in the
  2386. // element's style is replaced by what's pointed to by the `bypassed`
  2387. // field in the bypass property (i.e. restoring the property the
  2388. // bypass was overriding)
  2389. //
  2390. // for parsedProp:{ mapped: truthy }
  2391. // the generated flattenedProp:{ mapping: prop }
  2392. //
  2393. // for parsedProp:{ bypass: true }
  2394. // the generated flattenedProp:{ bypassed: parsedProp }
  2395. $$.styfn.applyParsedProperty = function( ele, parsedProp, context ){
  2396. parsedProp = $$.util.clone( parsedProp ); // copy b/c the same parsedProp may be applied to many elements, BUT
  2397. // the instances put in each element should be unique to avoid overwriting other the lists of other elements
  2398. var prop = parsedProp;
  2399. var style = ele._private.style;
  2400. var fieldVal, flatProp;
  2401. var type = $$.style.properties[ prop.name ].type;
  2402. var propIsBypass = prop.bypass;
  2403. var origProp = style[ prop.name ];
  2404. var origPropIsBypass = origProp && origProp.bypass;
  2405. // can't apply auto to width or height unless it's a parent node
  2406. if( (parsedProp.name === "height" || parsedProp.name === "width") && parsedProp.value === "auto" && ele.isNode() && !ele.isParent() ){
  2407. return false;
  2408. }
  2409. // check if we need to delete the current bypass
  2410. if( propIsBypass && prop.deleteBypass ){ // then this property is just here to indicate we need to delete
  2411. var currentProp = style[ prop.name ];
  2412. // can only delete if the current prop is a bypass and it points to the property it was overriding
  2413. if( !currentProp ){
  2414. return true; // property is already not defined
  2415. } else if( currentProp.bypass && currentProp.bypassed ){ // then replace the bypass property with the original
  2416. // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary)
  2417. style[ prop.name ] = currentProp.bypassed;
  2418. return true;
  2419. } else {
  2420. return false; // we're unsuccessful deleting the bypass
  2421. }
  2422. }
  2423. // put the property in the style objects
  2424. switch( prop.mapped ){ // flatten the property if mapped
  2425. case $$.style.types.mapData:
  2426. fieldVal = ele._private.data[ prop.field ];
  2427. if( !$$.is.number(fieldVal) ){ return false; } // it had better be a number
  2428. var percent = (fieldVal - prop.fieldMin) / (prop.fieldMax - prop.fieldMin);
  2429. if( type.color ){
  2430. var r1 = prop.valueMin[0];
  2431. var r2 = prop.valueMax[0];
  2432. var g1 = prop.valueMin[1];
  2433. var g2 = prop.valueMax[1];
  2434. var b1 = prop.valueMin[2];
  2435. var b2 = prop.valueMax[2];
  2436. var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3];
  2437. var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3];
  2438. var clr = [
  2439. Math.round( r1 + (r2 - r1)*percent ),
  2440. Math.round( g1 + (g2 - g1)*percent ),
  2441. Math.round( b1 + (b2 - b1)*percent ),
  2442. Math.round( a1 + (a2 - a1)*percent )
  2443. ];
  2444. flatProp = { // colours are simple, so just create the flat property instead of expensive string parsing
  2445. bypass: prop.bypass, // we're a bypass if the mapping property is a bypass
  2446. name: prop.name,
  2447. value: clr,
  2448. strValue: [ "rgba(", clr[0], ", ", clr[1], ", ", clr[2], ", ", clr[3] , ")" ].join("") // fake it til you make it
  2449. };
  2450. } else if( type.number ){
  2451. var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent;
  2452. flatProp = this.parse( prop.name, calcValue, prop.bypass );
  2453. } else {
  2454. return false; // can only map to colours and numbers
  2455. }
  2456. if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself
  2457. flatProp = this.parse( prop.name, origProp.strValue, prop.bypass);
  2458. }
  2459. flatProp.mapping = prop; // keep a reference to the mapping
  2460. prop = flatProp; // the flattened (mapped) property is the one we want
  2461. break;
  2462. case $$.style.types.data: // direct mapping
  2463. fieldVal = eval('ele._private.data.' + prop.field );
  2464. flatProp = this.parse( prop.name, fieldVal, prop.bypass );
  2465. if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself
  2466. flatProp = this.parse( prop.name, origProp.strValue, prop.bypass);
  2467. }
  2468. flatProp.mapping = prop; // keep a reference to the mapping
  2469. prop = flatProp; // the flattened (mapped) property is the one we want
  2470. break;
  2471. case undefined:
  2472. break; // just set the property
  2473. default:
  2474. return false; // danger, will robinson
  2475. }
  2476. // if the property is a bypass property, then link the resultant property to the original one
  2477. if( propIsBypass ){
  2478. if( origPropIsBypass ){ // then this bypass overrides the existing one
  2479. prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass
  2480. } else { // then link the orig prop to the new bypass
  2481. prop.bypassed = origProp;
  2482. }
  2483. style[ prop.name ] = prop; // and set
  2484. } else { // prop is not bypass
  2485. var prevProp;
  2486. if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop
  2487. prevProp = origProp.bypassed;
  2488. origProp.bypassed = prop;
  2489. } else { // then just replace the old prop with the new one
  2490. prevProp = style[ prop.name ];
  2491. style[ prop.name ] = prop;
  2492. }
  2493. if( prevProp && prevProp.mapping && prop.mapping && prevProp.context === context ){
  2494. prevProp = prevProp.prev;
  2495. }
  2496. if( prevProp && prevProp !== prop ){
  2497. prop.prev = prevProp;
  2498. }
  2499. }
  2500. prop.context = context;
  2501. return true;
  2502. };
  2503. $$.styfn.rollBackContext = function( ele, context ){
  2504. for( var j = 0; j < context.properties.length; j++ ){ // for each prop
  2505. var prop = context.properties[j];
  2506. var eleProp = ele._private.style[ prop.name ];
  2507. // because bypasses do not store prevs, look at the bypassed property
  2508. if( eleProp.bypassed ){
  2509. eleProp = eleProp.bypassed;
  2510. }
  2511. var first = true;
  2512. var lastEleProp;
  2513. var l = 0;
  2514. while( eleProp.prev ){
  2515. var prev = eleProp.prev;
  2516. if( eleProp.context === context ){
  2517. if( first ){
  2518. ele._private.style[ prop.name ] = prev;
  2519. } else if( lastEleProp ){
  2520. lastEleProp.prev = prev;
  2521. }
  2522. }
  2523. lastEleProp = eleProp;
  2524. eleProp = prev;
  2525. first = false;
  2526. l++;
  2527. // in case we have a problematic prev list
  2528. // if( l >= 100 ){
  2529. // debugger;
  2530. // }
  2531. }
  2532. }
  2533. };
  2534. // (potentially expensive calculation)
  2535. // apply the style to the element based on
  2536. // - its bypass
  2537. // - what selectors match it
  2538. $$.styfn.apply = function( eles ){
  2539. var self = this;
  2540. for( var ie = 0; ie < eles.length; ie++ ){
  2541. var ele = eles[ie];
  2542. if( self._private.newStyle ){
  2543. ele._private.styleCxts = [];
  2544. ele._private.style = {};
  2545. }
  2546. // apply the styles
  2547. for( var i = 0; i < this.length; i++ ){
  2548. var context = this[i];
  2549. var contextSelectorMatches = context.selector && context.selector.filter( ele ).length > 0; // NB: context.selector may be null for "core"
  2550. var props = context.properties;
  2551. if( contextSelectorMatches ){ // then apply its properties
  2552. // apply the properties in the context
  2553. for( var j = 0; j < props.length; j++ ){ // for each prop
  2554. var prop = props[j];
  2555. //if(prop.mapped) debugger;
  2556. if( !ele._private.styleCxts[i] || prop.mapped ){
  2557. this.applyParsedProperty( ele, prop, context );
  2558. }
  2559. }
  2560. // keep a note that this context matches
  2561. ele._private.styleCxts[i] = context;
  2562. } else {
  2563. // roll back style cxts that don't match now
  2564. if( ele._private.styleCxts[i] ){
  2565. this.rollBackContext( ele, context );
  2566. }
  2567. delete ele._private.styleCxts[i];
  2568. }
  2569. } // for context
  2570. } // for elements
  2571. self._private.newStyle = false;
  2572. };
  2573. // updates the visual style for all elements (useful for manual style modification after init)
  2574. $$.styfn.update = function(){
  2575. var cy = this._private.cy;
  2576. var eles = cy.elements();
  2577. eles.updateStyle();
  2578. };
  2579. // gets the rendered style for an element
  2580. $$.styfn.getRenderedStyle = function( ele ){
  2581. var ele = ele[0]; // insure it's an element
  2582. if( ele ){
  2583. var rstyle = {};
  2584. var style = ele._private.style;
  2585. var cy = this._private.cy;
  2586. var zoom = cy.zoom();
  2587. for( var i = 0; i < $$.style.properties.length; i++ ){
  2588. var prop = $$.style.properties[i];
  2589. var styleProp = style[ prop.name ];
  2590. if( styleProp ){
  2591. var val = styleProp.unitless ? styleProp.strValue : (styleProp.pxValue * zoom) + "px";
  2592. rstyle[ prop.name ] = val;
  2593. rstyle[ $$.util.dash2camel(prop.name) ] = val;
  2594. }
  2595. }
  2596. return rstyle;
  2597. }
  2598. };
  2599. // gets the raw style for an element
  2600. $$.styfn.getRawStyle = function( ele ){
  2601. var ele = ele[0]; // insure it's an element
  2602. if( ele ){
  2603. var rstyle = {};
  2604. var style = ele._private.style;
  2605. for( var i = 0; i < $$.style.properties.length; i++ ){
  2606. var prop = $$.style.properties[i];
  2607. var styleProp = style[ prop.name ];
  2608. if( styleProp ){
  2609. rstyle[ prop.name ] = styleProp.strValue;
  2610. rstyle[ $$.util.dash2camel(prop.name) ] = styleProp.strValue;
  2611. }
  2612. }
  2613. return rstyle;
  2614. }
  2615. };
  2616. // gets the value style for an element (useful for things like animations)
  2617. $$.styfn.getValueStyle = function( ele ){
  2618. var rstyle, style;
  2619. if( $$.is.element(ele) ){
  2620. rstyle = {};
  2621. style = ele._private.style;
  2622. } else {
  2623. rstyle = {};
  2624. style = ele; // just passed the style itself
  2625. }
  2626. if( style ){
  2627. for( var i = 0; i < $$.style.properties.length; i++ ){
  2628. var prop = $$.style.properties[i];
  2629. var styleProp = style[ prop.name ] || style[ $$.util.dash2camel(prop.name) ];
  2630. if( styleProp !== undefined && !$$.is.plainObject( styleProp ) ){ // then make a prop of it
  2631. styleProp = this.parse(prop.name, styleProp);
  2632. }
  2633. if( styleProp ){
  2634. var val = styleProp.value === undefined ? styleProp : styleProp.value;
  2635. rstyle[ prop.name ] = val;
  2636. rstyle[ $$.util.dash2camel(prop.name) ] = val;
  2637. }
  2638. }
  2639. }
  2640. return rstyle;
  2641. };
  2642. // just update the functional properties (i.e. mappings) in the elements'
  2643. // styles (less expensive than recalculation)
  2644. $$.styfn.updateFunctionalProperties = function( eles ){
  2645. for( var i = 0; i < eles.length; i++ ){ // for each ele
  2646. var ele = eles[i];
  2647. var style = ele._private.style;
  2648. for( var j = 0; j < $$.style.properties.length; j++ ){ // for each prop
  2649. var prop = $$.style.properties[j];
  2650. var propInStyle = style[ prop.name ];
  2651. if( propInStyle && propInStyle.mapping ){
  2652. var mapping = propInStyle.mapping;
  2653. this.applyParsedProperty( ele, mapping ); // reapply the mapping property
  2654. }
  2655. }
  2656. }
  2657. };
  2658. // bypasses are applied to an existing style on an element, and just tacked on temporarily
  2659. // returns true iff application was successful for at least 1 specified property
  2660. $$.styfn.applyBypass = function( eles, name, value ){
  2661. var props = [];
  2662. // put all the properties (can specify one or many) in an array after parsing them
  2663. if( name === "*" || name === "**" ){ // apply to all property names
  2664. if( value !== undefined ){
  2665. for( var i = 0; i < $$.style.properties.length; i++ ){
  2666. var prop = $$.style.properties[i];
  2667. var name = prop.name;
  2668. var parsedProp = this.parse(name, value, true);
  2669. if( parsedProp ){
  2670. props.push( parsedProp );
  2671. }
  2672. }
  2673. }
  2674. } else if( $$.is.string(name) ){ // then parse the single property
  2675. var parsedProp = this.parse(name, value, true);
  2676. if( parsedProp ){
  2677. props.push( parsedProp );
  2678. }
  2679. } else if( $$.is.plainObject(name) ){ // then parse each property
  2680. var specifiedProps = name;
  2681. for( var i = 0; i < $$.style.properties.length; i++ ){
  2682. var prop = $$.style.properties[i];
  2683. var name = prop.name;
  2684. var value = specifiedProps[ name ];
  2685. if( value === undefined ){ // try camel case name too
  2686. value = specifiedProps[ $$.util.dash2camel(name) ];
  2687. }
  2688. if( value !== undefined ){
  2689. var parsedProp = this.parse(name, value, true);
  2690. if( parsedProp ){
  2691. props.push( parsedProp );
  2692. }
  2693. }
  2694. }
  2695. } else { // can't do anything without well defined properties
  2696. return false;
  2697. }
  2698. // we've failed if there are no valid properties
  2699. if( props.length === 0 ){ return false; }
  2700. // now, apply the bypass properties on the elements
  2701. var ret = false; // return true if at least one succesful bypass applied
  2702. for( var i = 0; i < eles.length; i++ ){ // for each ele
  2703. var ele = eles[i];
  2704. for( var j = 0; j < props.length; j++ ){ // for each prop
  2705. var prop = props[j];
  2706. ret = this.applyParsedProperty( ele, prop ) || ret;
  2707. }
  2708. }
  2709. return ret;
  2710. };
  2711. $$.styfn.removeAllBypasses = function( eles ){
  2712. for( var i = 0; i < $$.style.properties.length; i++ ){
  2713. var prop = $$.style.properties[i];
  2714. var name = prop.name;
  2715. var value = ""; // empty => remove bypass
  2716. var parsedProp = this.parse(name, value, true);
  2717. for( var j = 0; j < eles.length; j++ ){
  2718. var ele = eles[j];
  2719. this.applyParsedProperty(ele, parsedProp);
  2720. }
  2721. }
  2722. };
  2723. })( cytoscape, typeof window === 'undefined' ? null : window );
  2724. ;(function($$){
  2725. var defaults = {
  2726. showOverlay: true,
  2727. hideEdgesOnViewport: false
  2728. };
  2729. var origDefaults = $$.util.copy( defaults );
  2730. $$.defaults = function( opts ){
  2731. defaults = $$.util.extend({}, origDefaults, opts);
  2732. };
  2733. $$.fn.core = function( fnMap, options ){
  2734. for( var name in fnMap ){
  2735. var fn = fnMap[name];
  2736. $$.Core.prototype[ name ] = fn;
  2737. }
  2738. };
  2739. $$.Core = function( opts ){
  2740. if( !(this instanceof $$.Core) ){
  2741. return new $$.Core(opts);
  2742. }
  2743. var cy = this;
  2744. opts = $$.util.extend({}, defaults, opts);
  2745. var container = opts.container;
  2746. var reg = $$.getRegistrationForInstance(cy, container);
  2747. if( reg && reg.cy ){
  2748. reg.domElement.innerHTML = '';
  2749. reg.cy.notify({ type: 'destroy' }); // destroy the renderer
  2750. $$.removeRegistrationForInstance(reg.cy, reg.domElement);
  2751. }
  2752. reg = $$.registerInstance( cy, container );
  2753. var readies = reg.readies;
  2754. var options = opts;
  2755. options.layout = $$.util.extend( { name: typeof window === 'undefined' ? "null" : "grid" }, options.layout );
  2756. options.renderer = $$.util.extend( { name: typeof window === 'undefined' ? "null" : "canvas" }, options.renderer );
  2757. // TODO determine whether we need a check like this even though we allow running headless now
  2758. //
  2759. // if( !$$.is.domElement(options.container) ){
  2760. // $$.util.error("Cytoscape.js must be called on an element");
  2761. // return;
  2762. // }
  2763. this._private = {
  2764. ready: false, // whether ready has been triggered
  2765. instanceId: reg.id, // the registered instance id
  2766. options: options, // cached options
  2767. elements: [], // array of elements
  2768. id2index: {}, // element id => index in elements array
  2769. listeners: [], // list of listeners
  2770. aniEles: [], // array of elements being animated
  2771. scratch: {}, // scratch object for core
  2772. layout: null,
  2773. renderer: null,
  2774. notificationsEnabled: true, // whether notifications are sent to the renderer
  2775. minZoom: 1e-50,
  2776. maxZoom: 1e50,
  2777. zoomEnabled: options.zoomEnabled === undefined ? true : options.zoomEnabled,
  2778. panEnabled: options.panEnabled === undefined ? true : options.panEnabled,
  2779. boxSelectionEnabled: options.boxSelectionEnabled === undefined ? true : options.boxSelectionEnabled,
  2780. zoom: $$.is.number(options.zoom) ? options.zoom : 1,
  2781. pan: {
  2782. x: $$.is.plainObject(options.pan) && $$.is.number(options.pan.x) ? options.pan.x : 0,
  2783. y: $$.is.plainObject(options.pan) && $$.is.number(options.pan.y) ? options.pan.y : 0,
  2784. },
  2785. hasCompoundNodes: false
  2786. };
  2787. // init zoom bounds
  2788. if( $$.is.number(options.minZoom) && $$.is.number(options.maxZoom) && options.minZoom < options.maxZoom ){
  2789. this._private.minZoom = options.minZoom;
  2790. this._private.maxZoom = options.maxZoom;
  2791. } else if( $$.is.number(options.minZoom) && options.maxZoom === undefined ){
  2792. this._private.minZoom = options.minZoom;
  2793. } else if( $$.is.number(options.maxZoom) && options.minZoom === undefined ){
  2794. this._private.maxZoom = options.maxZoom;
  2795. }
  2796. // init style
  2797. this._private.style = $$.is.stylesheet(options.style) ? options.style.generateStyle(this) : ( $$.is.array(options.style) ? $$.style.fromJson(this, options.style) : new $$.Style( cy ) );
  2798. cy.initRenderer( $$.util.extend({
  2799. showOverlay: options.showOverlay,
  2800. hideEdgesOnViewport: options.hideEdgesOnViewport
  2801. }, options.renderer) );
  2802. // initial load
  2803. cy.load(options.elements, function(){ // onready
  2804. cy.startAnimationLoop();
  2805. cy._private.ready = true;
  2806. // if a ready callback is specified as an option, the bind it
  2807. if( $$.is.fn( options.ready ) ){
  2808. cy.bind("ready", options.ready);
  2809. }
  2810. // bind all the ready handlers registered before creating this instance
  2811. for( var i = 0; i < readies.length; i++ ){
  2812. var fn = readies[i];
  2813. cy.bind("ready", fn);
  2814. }
  2815. reg.readies = []; // clear b/c we've bound them all and don't want to keep it around in case a new core uses the same div etc
  2816. cy.trigger("ready");
  2817. }, options.done);
  2818. };
  2819. $$.corefn = $$.Core.prototype; // short alias
  2820. $$.fn.core({
  2821. ready: function(){
  2822. return this._private.ready;
  2823. },
  2824. registered: function(){
  2825. if( this._private && this._private.instanceId != null ){
  2826. return true;
  2827. } else {
  2828. return false;
  2829. }
  2830. },
  2831. registeredId: function(){
  2832. return this._private.instanceId;
  2833. },
  2834. getElementById: function( id ){
  2835. var index = this._private.id2index[ id ];
  2836. if( index !== undefined ){
  2837. return this._private.elements[ index ];
  2838. }
  2839. // worst case, return an empty collection
  2840. return new $$.Collection( this );
  2841. },
  2842. hasCompoundNodes: function(){
  2843. return this._private.hasCompoundNodes;
  2844. },
  2845. addToPool: function( eles ){
  2846. var elements = this._private.elements;
  2847. var id2index = this._private.id2index;
  2848. for( var i = 0; i < eles.length; i++ ){
  2849. var ele = eles[i];
  2850. var id = ele._private.data.id;
  2851. var index = id2index[ id ];
  2852. var alreadyInPool = index !== undefined;
  2853. if( !alreadyInPool ){
  2854. index = elements.length;
  2855. elements.push( ele )
  2856. id2index[ id ] = index;
  2857. ele._private.index = index;
  2858. }
  2859. }
  2860. return this; // chaining
  2861. },
  2862. removeFromPool: function( eles ){
  2863. var elements = this._private.elements;
  2864. var id2index = this._private.id2index;
  2865. for( var i = 0; i < eles.length; i++ ){
  2866. var ele = eles[i];
  2867. var id = ele._private.data.id;
  2868. var index = id2index[ id ];
  2869. var inPool = index !== undefined;
  2870. if( inPool ){
  2871. delete this._private.id2index[ id ];
  2872. elements.splice(index, 1);
  2873. // adjust the index of all elements past this index
  2874. for( var j = index; j < elements.length; j++ ){
  2875. var jid = elements[j]._private.data.id;
  2876. id2index[ jid ]--;
  2877. }
  2878. }
  2879. }
  2880. },
  2881. container: function(){
  2882. return this._private.options.container;
  2883. },
  2884. options: function(){
  2885. return $$.util.copy( this._private.options );
  2886. },
  2887. json: function(params){
  2888. var json = {};
  2889. var cy = this;
  2890. json.elements = {};
  2891. cy.elements().each(function(i, ele){
  2892. var group = ele.group();
  2893. if( !json.elements[group] ){
  2894. json.elements[group] = [];
  2895. }
  2896. json.elements[group].push( ele.json() );
  2897. });
  2898. json.style = cy.style();
  2899. json.scratch = cy.scratch();
  2900. json.zoomEnabled = cy._private.zoomEnabled;
  2901. json.panEnabled = cy._private.panEnabled;
  2902. json.layout = cy._private.options.layout;
  2903. json.renderer = cy._private.options.renderer;
  2904. return json;
  2905. }
  2906. });
  2907. })( cytoscape );
  2908. (function($$, window){
  2909. $$.fn.core({
  2910. add: function(opts){
  2911. var elements;
  2912. var cy = this;
  2913. // add the elements
  2914. if( $$.is.elementOrCollection(opts) ){
  2915. var eles = opts;
  2916. var jsons = [];
  2917. for( var i = 0; i < eles.length; i++ ){
  2918. var ele = eles[i];
  2919. jsons.push( ele.json() );
  2920. }
  2921. elements = new $$.Collection( cy, jsons );
  2922. }
  2923. // specify an array of options
  2924. else if( $$.is.array(opts) ){
  2925. var jsons = opts;
  2926. elements = new $$.Collection(cy, jsons);
  2927. }
  2928. // specify via opts.nodes and opts.edges
  2929. else if( $$.is.plainObject(opts) && ($$.is.array(opts.nodes) || $$.is.array(opts.edges)) ){
  2930. var elesByGroup = opts;
  2931. var jsons = [];
  2932. var grs = ["nodes", "edges"];
  2933. for( var i = 0, il = grs.length; i < il; i++ ){
  2934. var group = grs[i];
  2935. var elesArray = elesByGroup[group];
  2936. if( $$.is.array(elesArray) ){
  2937. for( var j = 0, jl = elesArray.length; j < jl; j++ ){
  2938. var json = elesArray[j];
  2939. var mjson = $$.util.extend({}, json, { group: group });
  2940. jsons.push( mjson );
  2941. }
  2942. }
  2943. }
  2944. elements = new $$.Collection(cy, jsons);
  2945. }
  2946. // specify options for one element
  2947. else {
  2948. var json = opts;
  2949. elements = (new $$.Element( cy, json )).collection();
  2950. }
  2951. return elements.filter(function(){
  2952. return !this.removed();
  2953. });
  2954. },
  2955. remove: function(collection){
  2956. if( $$.is.elementOrCollection(collection) ){
  2957. collection = collection;
  2958. } else if( $$.is.string(collection) ){
  2959. var selector = collection;
  2960. collection = this.$( selector );
  2961. }
  2962. return collection.remove();
  2963. },
  2964. load: function(elements, onload, ondone){
  2965. var cy = this;
  2966. // remove old elements
  2967. var oldEles = cy.elements();
  2968. if( oldEles.length > 0 ){
  2969. oldEles.remove();
  2970. }
  2971. cy.notifications(false);
  2972. var processedElements = [];
  2973. if( elements != null ){
  2974. if( $$.is.plainObject(elements) || $$.is.array(elements) ){
  2975. cy.add( elements );
  2976. }
  2977. }
  2978. function callback(){
  2979. cy.one("layoutready", function(e){
  2980. cy.notifications(true);
  2981. cy.trigger(e); // we missed this event by turning notifications off, so pass it on
  2982. cy.notify({
  2983. type: "load",
  2984. collection: cy.elements(),
  2985. style: cy._private.style
  2986. });
  2987. cy.one("load", onload);
  2988. cy.trigger("load");
  2989. }).one("layoutstop", function(){
  2990. cy.one("done", ondone);
  2991. cy.trigger("done");
  2992. });
  2993. cy.layout( cy._private.options.layout );
  2994. }
  2995. // TODO remove timeout when chrome reports dimensions onload properly
  2996. // TODO investigate dimensions reporting issue (also affects safari/ios)
  2997. if( true || window && window.chrome ){
  2998. setTimeout(function(){
  2999. callback();
  3000. }, 30);
  3001. } else {
  3002. callback();
  3003. }
  3004. return this;
  3005. }
  3006. });
  3007. })( cytoscape, typeof window === 'undefined' ? null : window );
  3008. ;(function($$){
  3009. $$.fn.core({
  3010. addToAnimationPool: function( eles ){
  3011. var cy = this;
  3012. var aniEles = cy._private.aniEles;
  3013. var aniElesHas = [];
  3014. for( var i = 0; i < aniEles.length; i++ ){
  3015. var id = aniEles[i]._private.data.id;
  3016. aniElesHas[ id ] = true;
  3017. }
  3018. for( var i = 0; i < eles.length; i++ ){
  3019. var ele = eles[i];
  3020. var id = ele._private.data.id;
  3021. if( !aniElesHas[id] ){
  3022. aniEles.push( ele );
  3023. }
  3024. }
  3025. },
  3026. startAnimationLoop: function(){
  3027. var cy = this;
  3028. var stepDelay = 1000/60;
  3029. var useTimeout = false;
  3030. var useRequestAnimationFrame = true;
  3031. // initialise the list
  3032. cy._private.aniEles = [];
  3033. // TODO change this when standardised
  3034. var requestAnimationFrame = typeof window === 'undefined' ? function(){} : ( window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  3035. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame );
  3036. if( requestAnimationFrame == null || !useRequestAnimationFrame ){
  3037. requestAnimationFrame = function(fn){
  3038. window.setTimeout(function(){
  3039. fn(+new Date);
  3040. }, stepDelay);
  3041. };
  3042. }
  3043. var containerDom = cy.container();
  3044. function globalAnimationStep(){
  3045. function exec(){
  3046. requestAnimationFrame(function(now){
  3047. handleElements(now);
  3048. globalAnimationStep();
  3049. }, containerDom);
  3050. }
  3051. if( useTimeout ){
  3052. setTimeout(function(){
  3053. exec();
  3054. }, stepDelay);
  3055. } else {
  3056. exec();
  3057. }
  3058. }
  3059. globalAnimationStep(); // first call
  3060. function handleElements(now){
  3061. now = +new Date;
  3062. var eles = cy._private.aniEles;
  3063. for( var e = 0; e < eles.length; e++ ){
  3064. var ele = eles[e];
  3065. // we might have errors if we edit animation.queue and animation.current
  3066. // for ele (i.e. by stopping)
  3067. // try{
  3068. var current = ele._private.animation.current;
  3069. var queue = ele._private.animation.queue;
  3070. // if nothing currently animating, get something from the queue
  3071. if( current.length === 0 ){
  3072. var q = queue;
  3073. var next = q.length > 0 ? q.shift() : null;
  3074. if( next != null ){
  3075. next.callTime = +new Date; // was queued, so update call time
  3076. current.push( next );
  3077. }
  3078. }
  3079. // step and remove if done
  3080. var completes = [];
  3081. for(var i = 0; i < current.length; i++){
  3082. var ani = current[i];
  3083. step( ele, ani, now );
  3084. if( current[i].done ){
  3085. completes.push( ani );
  3086. // remove current[i]
  3087. current.splice(i, 1);
  3088. i--;
  3089. }
  3090. }
  3091. // call complete callbacks
  3092. for( var i = 0; i < completes.length; i++ ){
  3093. var ani = completes[i];
  3094. var complete = ani.params.complete;
  3095. if( $$.is.fn(complete) ){
  3096. complete.apply( ele, [ now ] );
  3097. }
  3098. }
  3099. // } catch(e){
  3100. // // do nothing
  3101. // }
  3102. } // each element
  3103. // notify renderer
  3104. if( eles.length > 0 ){
  3105. cy.notify({
  3106. type: "draw",
  3107. collection: eles
  3108. });
  3109. }
  3110. // remove elements from list of currently animating if its queues are empty
  3111. for( var i = 0; i < eles.length; i++ ){
  3112. var ele = eles[i];
  3113. var queue = ele._private.animation.queue;
  3114. var current = ele._private.animation.current;
  3115. var keepEle = current.length > 0 || queue.length > 0;
  3116. if( !keepEle ){ // then remove from the array
  3117. eles.splice(i, 1);
  3118. i--;
  3119. }
  3120. }
  3121. } // handleElements
  3122. function step( self, animation, now ){
  3123. var style = cy._private.style;
  3124. var properties = animation.properties;
  3125. var params = animation.params;
  3126. var startTime = animation.callTime;
  3127. var percent;
  3128. if( animation.duration === 0 ){
  3129. percent = 1;
  3130. } else {
  3131. percent = Math.min(1, (now - startTime)/animation.duration);
  3132. }
  3133. if( percent < 0 ){
  3134. percent = 0;
  3135. } else if( percent > 1 ){
  3136. percent = 1;
  3137. }
  3138. if( properties.delay == null ){ // then update the position
  3139. var startPos = animation.startPosition;
  3140. var endPos = properties.position;
  3141. var pos = self._private.position;
  3142. if( endPos ){
  3143. if( valid( startPos.x, endPos.x ) ){
  3144. pos.x = ease( startPos.x, endPos.x, percent );
  3145. }
  3146. if( valid( startPos.y, endPos.y ) ){
  3147. pos.y = ease( startPos.y, endPos.y, percent );
  3148. }
  3149. }
  3150. if( properties.css ){
  3151. var props = $$.style.properties;
  3152. for( var i = 0; i < props.length; i++ ){
  3153. var name = props[i].name;
  3154. var end = properties.css[ name ];
  3155. if( end !== undefined ){
  3156. var start = animation.startStyle[ name ];
  3157. var easedVal = ease( start, end, percent );
  3158. style.applyBypass( self, name, easedVal );
  3159. }
  3160. } // for props
  3161. } // if
  3162. }
  3163. if( $$.is.fn(params.step) ){
  3164. params.step.apply( self, [ now ] );
  3165. }
  3166. if( percent >= 1 ){
  3167. animation.done = true;
  3168. }
  3169. return percent;
  3170. }
  3171. function valid(start, end){
  3172. if( start == null || end == null ){
  3173. return false;
  3174. }
  3175. if( $$.is.number(start) && $$.is.number(end) ){
  3176. return true;
  3177. } else if( (start) && (end) ){
  3178. return true;
  3179. }
  3180. return false;
  3181. }
  3182. function ease(start, end, percent){
  3183. if( percent < 0 ){
  3184. percent = 0;
  3185. } else if( percent > 1 ){
  3186. percent = 1;
  3187. }
  3188. if( $$.is.number(start) && $$.is.number(end) ){
  3189. return start + (end - start) * percent;
  3190. } else if( $$.is.number(start[0]) && $$.is.number(end[0]) ){ // then assume a colour
  3191. var c1 = start;
  3192. var c2 = end;
  3193. function ch(ch1, ch2){
  3194. var diff = ch2 - ch1;
  3195. var min = ch1;
  3196. return Math.round( percent * diff + min );
  3197. }
  3198. var r = ch( c1[0], c2[0] );
  3199. var g = ch( c1[1], c2[1] );
  3200. var b = ch( c1[2], c2[2] );
  3201. return 'rgb(' + r + ', ' + g + ', ' + b + ')';
  3202. }
  3203. return undefined;
  3204. }
  3205. }
  3206. });
  3207. })( cytoscape );
  3208. ;(function($$){
  3209. $$.fn.core({
  3210. data: $$.define.data({
  3211. field: "data",
  3212. bindingEvent: "data",
  3213. allowBinding: true,
  3214. allowSetting: true,
  3215. settingEvent: "data",
  3216. settingTriggersEvent: true,
  3217. triggerFnName: "trigger",
  3218. allowGetting: true
  3219. }),
  3220. removeData: $$.define.removeData({
  3221. field: "data",
  3222. event: "data",
  3223. triggerFnName: "trigger",
  3224. triggerEvent: true
  3225. }),
  3226. batchData: $$.define.batchData({
  3227. field: "data",
  3228. event: "data",
  3229. triggerFnName: "trigger",
  3230. immutableKeys: {
  3231. "id": true,
  3232. "source": true,
  3233. "target": true,
  3234. "parent": true
  3235. },
  3236. updateMappers: true
  3237. }),
  3238. scratch: $$.define.data({
  3239. field: "scratch",
  3240. allowBinding: false,
  3241. allowSetting: true,
  3242. settingTriggersEvent: false,
  3243. allowGetting: true
  3244. }),
  3245. removeScratch: $$.define.removeData({
  3246. field: "scratch",
  3247. triggerEvent: false
  3248. }),
  3249. });
  3250. })( cytoscape );
  3251. ;(function($$){
  3252. $$.fn.core({
  3253. on: $$.define.on(), // .on( events [, selector] [, data], handler)
  3254. one: $$.define.on({ unbindSelfOnTrigger: true }),
  3255. once: $$.define.on({ unbindAllBindersOnTrigger: true }),
  3256. off: $$.define.off(), // .off( events [, selector] [, handler] )
  3257. trigger: $$.define.trigger(), // .trigger( events [, extraParams] )
  3258. });
  3259. // aliases for those folks who like old stuff:
  3260. $$.corefn.bind = $$.corefn.on;
  3261. $$.corefn.unbind = $$.corefn.off;
  3262. // add event aliases like .click()
  3263. $$.define.event.aliasesOn( $$.corefn );
  3264. })( cytoscape );
  3265. ;(function($$){
  3266. $$.fn.core({
  3267. png: function(){
  3268. var cy = this;
  3269. var renderer = this._private.renderer;
  3270. return renderer.png();
  3271. }
  3272. });
  3273. })( cytoscape );
  3274. ;(function($$){
  3275. $$.fn.core({
  3276. layout: function( params ){
  3277. var cy = this;
  3278. if( this._private.layoutRunning ){ // don't run another layout if one's already going
  3279. return this;
  3280. }
  3281. // if no params, use the previous ones
  3282. if( params == null ){
  3283. params = this._private.options.layout;
  3284. }
  3285. this.initLayout( params );
  3286. cy.trigger("layoutstart");
  3287. this._private.layoutRunning = true;
  3288. this.one('layoutstop', function(){
  3289. this._private.layoutRunning = false;
  3290. });
  3291. this._private.layout.run();
  3292. return this;
  3293. },
  3294. initLayout: function( options ){
  3295. if( options == null ){
  3296. $$.util.error("Layout options must be specified to run a layout");
  3297. return;
  3298. }
  3299. if( options.name == null ){
  3300. $$.util.error("A `name` must be specified to run a layout");
  3301. return;
  3302. }
  3303. var name = options.name;
  3304. var layoutProto = $$.extension("layout", name);
  3305. if( layoutProto == null ){
  3306. $$.util.error("Can not apply layout: No such layout `%s` found; did you include its JS file?", name);
  3307. return;
  3308. }
  3309. this._private.layout = new layoutProto( $$.util.extend({}, options, {
  3310. renderer: this._private.renderer,
  3311. cy: this
  3312. }) );
  3313. this._private.options.layout = options; // save options
  3314. }
  3315. });
  3316. })( cytoscape );
  3317. (function($$){
  3318. $$.fn.core({
  3319. notify: function( params ){
  3320. if( !this._private.notificationsEnabled ){ return; } // exit on disabled
  3321. var renderer = this.renderer();
  3322. var cy = this;
  3323. // normalise params.collection
  3324. if( $$.is.element(params.collection) ){ // make collection from element
  3325. var element = params.collection;
  3326. params.collection = new $$.Collection(cy, [ element ]);
  3327. } else if( $$.is.array(params.collection) ){ // make collection from elements array
  3328. var elements = params.collection;
  3329. params.collection = new $$.Collection(cy, elements);
  3330. }
  3331. renderer.notify(params);
  3332. },
  3333. notifications: function( bool ){
  3334. var p = this._private;
  3335. if( bool === undefined ){
  3336. return p.notificationsEnabled;
  3337. } else {
  3338. p.notificationsEnabled = bool ? true : false;
  3339. }
  3340. },
  3341. noNotifications: function( callback ){
  3342. this.notifications(false);
  3343. callback();
  3344. this.notifications(true);
  3345. }
  3346. });
  3347. })( cytoscape );
  3348. ;(function($$){
  3349. $$.fn.core({
  3350. renderTo: function( context, zoom, pan ){
  3351. var r = this._private.renderer;
  3352. r.renderTo( context, zoom, pan );
  3353. },
  3354. renderer: function(){
  3355. return this._private.renderer;
  3356. },
  3357. initRenderer: function( options ){
  3358. var cy = this;
  3359. var rendererProto = $$.extension("renderer", options.name);
  3360. if( rendererProto == null ){
  3361. $$.util.error("Can not initialise: No such renderer `%s` found; did you include its JS file?", options.name);
  3362. return;
  3363. }
  3364. this._private.renderer = new rendererProto(
  3365. $$.util.extend({}, options, {
  3366. cy: cy,
  3367. style: cy._private.style
  3368. })
  3369. );
  3370. }
  3371. });
  3372. })( cytoscape );
  3373. ;(function($$){
  3374. $$.fn.core({
  3375. // get a collection
  3376. // - empty collection on no args
  3377. // - collection of elements in the graph on selector arg
  3378. // - guarantee a returned collection when elements or collection specified
  3379. collection: function( eles ){
  3380. if( $$.is.string(eles) ){
  3381. return this.$( eles );
  3382. } else if( $$.is.elementOrCollection(eles) ){
  3383. return eles.collection();
  3384. }
  3385. return new $$.Collection( this );
  3386. },
  3387. nodes: function( selector ){
  3388. var nodes = this.$("node");
  3389. if( selector ){
  3390. return nodes.filter( selector );
  3391. }
  3392. return nodes;
  3393. },
  3394. edges: function( selector ){
  3395. var edges = this.$("edge");
  3396. if( selector ){
  3397. return edges.filter( selector );
  3398. }
  3399. return edges;
  3400. },
  3401. // search the graph like jQuery
  3402. $: function( selector ){
  3403. var eles = new $$.Collection( this, this._private.elements );
  3404. if( selector ){
  3405. return eles.filter( selector );
  3406. }
  3407. return eles;
  3408. }
  3409. });
  3410. // aliases
  3411. $$.corefn.elements = $$.corefn.filter = $$.corefn.$;
  3412. })( cytoscape );
  3413. ;(function($$){
  3414. $$.fn.core({
  3415. style: function(val){
  3416. return this._private.style;
  3417. }
  3418. });
  3419. })( cytoscape );
  3420. ;(function($$){
  3421. $$.fn.core({
  3422. panningEnabled: function( bool ){
  3423. if( bool !== undefined ){
  3424. this._private.panEnabled = bool ? true : false;
  3425. } else {
  3426. return this._private.panEnabled;
  3427. }
  3428. return this; // chaining
  3429. },
  3430. zoomingEnabled: function( bool ){
  3431. if( bool !== undefined ){
  3432. this._private.zoomEnabled = bool ? true : false;
  3433. } else {
  3434. return this._private.zoomEnabled;
  3435. }
  3436. return this; // chaining
  3437. },
  3438. boxSelectionEnabled: function( bool ){
  3439. if( bool !== undefined ){
  3440. this._private.boxSelectionEnabled = bool ? true : false;
  3441. } else {
  3442. return this._private.boxSelectionEnabled;
  3443. }
  3444. return this; // chaining
  3445. },
  3446. pan: function(){
  3447. var args = arguments;
  3448. var pan = this._private.pan;
  3449. var dim, val, dims, x, y;
  3450. switch( args.length ){
  3451. case 0: // .pan()
  3452. return pan;
  3453. case 1:
  3454. if( !this._private.panEnabled ){
  3455. return this;
  3456. } else if( $$.is.string( args[0] ) ){ // .pan("x")
  3457. dim = args[0];
  3458. return pan[ dim ];
  3459. } else if( $$.is.plainObject( args[0] ) ) { // .pan({ x: 0, y: 100 })
  3460. dims = args[0];
  3461. x = dims.x;
  3462. y = dims.y;
  3463. if( $$.is.number(x) ){
  3464. pan.x = x;
  3465. }
  3466. if( $$.is.number(y) ){
  3467. pan.y = y;
  3468. }
  3469. this.trigger("pan");
  3470. }
  3471. break;
  3472. case 2: // .pan("x", 100)
  3473. if( !this._private.panEnabled ){
  3474. return this;
  3475. }
  3476. dim = args[0];
  3477. val = args[1];
  3478. if( (dim === "x" || dim === "y") && $$.is.number(val) ){
  3479. pan[dim] = val;
  3480. }
  3481. this.trigger("pan");
  3482. break;
  3483. default:
  3484. break; // invalid
  3485. }
  3486. this.notify({ // notify the renderer that the viewport changed
  3487. type: "viewport"
  3488. });
  3489. return this; // chaining
  3490. },
  3491. panBy: function(params){
  3492. var args = arguments;
  3493. var pan = this._private.pan;
  3494. var dim, val, dims, x, y;
  3495. if( !this._private.panEnabled ){
  3496. return this;
  3497. }
  3498. switch( args.length ){
  3499. case 1:
  3500. if( $$.is.plainObject( args[0] ) ) { // .panBy({ x: 0, y: 100 })
  3501. dims = args[0];
  3502. x = dims.x;
  3503. y = dims.y;
  3504. if( $$.is.number(x) ){
  3505. pan.x += x;
  3506. }
  3507. if( $$.is.number(y) ){
  3508. pan.y += y;
  3509. }
  3510. this.trigger("pan");
  3511. }
  3512. break;
  3513. case 2: // .panBy("x", 100)
  3514. dim = args[0];
  3515. val = args[1];
  3516. if( (dim === "x" || dim === "y") && $$.is.number(val) ){
  3517. pan[dim] += val;
  3518. }
  3519. this.trigger("pan");
  3520. break;
  3521. default:
  3522. break; // invalid
  3523. }
  3524. this.notify({ // notify the renderer that the viewport changed
  3525. type: "viewport"
  3526. });
  3527. return this; // chaining
  3528. },
  3529. fit: function( elements, padding ){
  3530. if( $$.is.number(elements) && padding === undefined ){ // elements is optional
  3531. padding = elements;
  3532. elements = undefined;
  3533. }
  3534. if( !this._private.panEnabled || !this._private.zoomEnabled ){
  3535. return this;
  3536. }
  3537. if( $$.is.string(elements) ){
  3538. var sel = elements;
  3539. elements = this.$( sel );
  3540. } else if( !$$.is.elementOrCollection(elements) ){
  3541. elements = this.elements();
  3542. }
  3543. var bb = elements.boundingBox();
  3544. var style = this.style();
  3545. var w = parseFloat( style.containerCss("width") );
  3546. var h = parseFloat( style.containerCss("height") );
  3547. var zoom;
  3548. padding = $$.is.number(padding) ? padding : 0;
  3549. if( !isNaN(w) && !isNaN(h) ){
  3550. zoom = this._private.zoom = Math.min( (w - 2*padding)/bb.w, (h - 2*padding)/bb.h );
  3551. // crop zoom
  3552. zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom;
  3553. zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom;
  3554. this._private.pan = { // now pan to middle
  3555. x: (w - zoom*( bb.x1 + bb.x2 ))/2,
  3556. y: (h - zoom*( bb.y1 + bb.y2 ))/2
  3557. };
  3558. }
  3559. this.trigger("pan zoom");
  3560. this.notify({ // notify the renderer that the viewport changed
  3561. type: "viewport"
  3562. });
  3563. return this; // chaining
  3564. },
  3565. minZoom: function( zoom ){
  3566. if( zoom === undefined ){
  3567. return this._private.minZoom;
  3568. } else if( $$.is.number(zoom) ){
  3569. this._private.minZoom = zoom;
  3570. }
  3571. return this;
  3572. },
  3573. maxZoom: function( zoom ){
  3574. if( zoom === undefined ){
  3575. return this._private.maxZoom;
  3576. } else if( $$.is.number(zoom) ){
  3577. this._private.maxZoom = zoom;
  3578. }
  3579. return this;
  3580. },
  3581. zoom: function( params ){
  3582. var pos;
  3583. var zoom;
  3584. if( params === undefined ){ // then get the zoom
  3585. return this._private.zoom;
  3586. } else if( $$.is.number(params) ){ // then set the zoom
  3587. zoom = params;
  3588. pos = {
  3589. x: 0,
  3590. y: 0
  3591. };
  3592. } else if( $$.is.plainObject(params) ){ // then zoom about a point
  3593. zoom = params.level;
  3594. if( params.renderedPosition ){
  3595. var rpos = params.renderedPosition;
  3596. var p = this._private.pan;
  3597. var z = this._private.zoom;
  3598. pos = {
  3599. x: (rpos.x - p.x)/z,
  3600. y: (rpos.y - p.y)/z
  3601. };
  3602. } else if( params.position ){
  3603. pos = params.position;
  3604. }
  3605. if( pos && !this._private.panEnabled ){
  3606. return this; // panning disabled
  3607. }
  3608. }
  3609. if( !this._private.zoomEnabled ){
  3610. return this; // zooming disabled
  3611. }
  3612. if( !$$.is.number(zoom) || !$$.is.number(pos.x) || !$$.is.number(pos.y) ){
  3613. return this; // can't zoom with invalid params
  3614. }
  3615. // crop zoom
  3616. zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom;
  3617. zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom;
  3618. var pan1 = this._private.pan;
  3619. var zoom1 = this._private.zoom;
  3620. var zoom2 = zoom;
  3621. var pan2 = {
  3622. x: -zoom2/zoom1 * (pos.x - pan1.x) + pos.x,
  3623. y: -zoom2/zoom1 * (pos.y - pan1.y) + pos.y
  3624. };
  3625. this._private.zoom = zoom;
  3626. this._private.pan = pan2;
  3627. var posChanged = pan1.x !== pan2.x || pan1.y !== pan2.y;
  3628. this.trigger("zoom" + (posChanged ? " pan" : "") );
  3629. this.notify({ // notify the renderer that the viewport changed
  3630. type: "viewport"
  3631. });
  3632. return this; // chaining
  3633. },
  3634. // get the bounding box of the elements (in raw model position)
  3635. boundingBox: function( selector ){
  3636. var eles = this.$( selector );
  3637. return eles.boundingBox();
  3638. },
  3639. center: function(elements){
  3640. if( !this._private.panEnabled || !this._private.zoomEnabled ){
  3641. return this;
  3642. }
  3643. if( $$.is.string(elements) ){
  3644. var selector = elements;
  3645. elements = cy.elements( selector );
  3646. } else if( !$$.is.elementOrCollection(elements) ){
  3647. elements = cy.elements();
  3648. }
  3649. var bb = elements.boundingBox();
  3650. var style = this.style();
  3651. var w = parseFloat( style.containerCss("width") );
  3652. var h = parseFloat( style.containerCss("height") );
  3653. var zoom = this._private.zoom;
  3654. this.pan({ // now pan to middle
  3655. x: (w - zoom*( bb.x1 + bb.x2 ))/2,
  3656. y: (h - zoom*( bb.y1 + bb.y2 ))/2
  3657. });
  3658. this.trigger("pan");
  3659. this.notify({ // notify the renderer that the viewport changed
  3660. type: "viewport"
  3661. });
  3662. return this; // chaining
  3663. },
  3664. reset: function(){
  3665. if( !this._private.panEnabled || !this._private.zoomEnabled ){
  3666. return this;
  3667. }
  3668. this.pan({ x: 0, y: 0 });
  3669. if( this._private.maxZoom > 1 && this._private.minZoom < 1 ){
  3670. this.zoom(1);
  3671. }
  3672. this.notify({ // notify the renderer that the viewport changed
  3673. type: "viewport"
  3674. });
  3675. return this; // chaining
  3676. }
  3677. });
  3678. })( cytoscape );
  3679. ;(function($$){
  3680. // Use this interface to define functions for collections/elements.
  3681. // This interface is good, because it forces you to think in terms
  3682. // of the collections case (more than 1 element), so we don't need
  3683. // notification blocking nonsense everywhere.
  3684. //
  3685. // Other collection-*.js files depend on this being defined first.
  3686. // It's a trade off: It simplifies the code for Collection and
  3687. // Element integration so much that it's worth it to create the
  3688. // JS dependency.
  3689. //
  3690. // Having this integration guarantees that we can call any
  3691. // collection function on an element and vice versa.
  3692. $$.fn.collection = $$.fn.eles = function( fnMap, options ){
  3693. for( var name in fnMap ){
  3694. var fn = fnMap[name];
  3695. $$.Collection.prototype[ name ] = fn;
  3696. }
  3697. };
  3698. // factory for generating edge ids when no id is specified for a new element
  3699. var idFactory = {
  3700. prefix: {
  3701. nodes: "n",
  3702. edges: "e"
  3703. },
  3704. id: {
  3705. nodes: 0,
  3706. edges: 0
  3707. },
  3708. generate: function(cy, element, tryThisId){
  3709. var json = $$.is.element( element ) ? element._private : element;
  3710. var group = json.group;
  3711. var id = tryThisId != null ? tryThisId : this.prefix[group] + this.id[group];
  3712. if( cy.getElementById(id).empty() ){
  3713. this.id[group]++; // we've used the current id, so move it up
  3714. } else { // otherwise keep trying successive unused ids
  3715. while( !cy.getElementById(id).empty() ){
  3716. id = this.prefix[group] + ( ++this.id[group] );
  3717. }
  3718. }
  3719. return id;
  3720. }
  3721. };
  3722. // Element
  3723. ////////////////////////////////////////////////////////////////////////////////////////////////////
  3724. // represents a node or an edge
  3725. $$.Element = function(cy, params, restore){
  3726. if( !(this instanceof $$.Element) ){
  3727. return new $$.Element(cy, params, restore);
  3728. }
  3729. var self = this;
  3730. restore = (restore === undefined || restore ? true : false);
  3731. if( cy === undefined || params === undefined || !$$.is.core(cy) ){
  3732. $$.util.error("An element must have a core reference and parameters set");
  3733. return;
  3734. }
  3735. // validate group
  3736. if( params.group !== "nodes" && params.group !== "edges" ){
  3737. $$.util.error("An element must be of type `nodes` or `edges`; you specified `" + params.group + "`");
  3738. return;
  3739. }
  3740. // make the element array-like, just like a collection
  3741. this.length = 1;
  3742. this[0] = this;
  3743. // NOTE: when something is added here, add also to ele.json()
  3744. this._private = {
  3745. cy: cy,
  3746. single: true, // indicates this is an element
  3747. data: params.data || {}, // data object
  3748. position: params.position || {}, // fields x, y, etc (could be 3d or radial coords; renderer decides)
  3749. autoWidth: undefined, // width and height of nodes calculated by the renderer when set to special "auto" value
  3750. autoHeight: undefined,
  3751. listeners: [], // array of bound listeners
  3752. group: params.group, // string; "nodes" or "edges"
  3753. style: {}, // properties as set by the style
  3754. rstyle: {}, // properties for style sent from the renderer to the core
  3755. styleCxts: [], // applied style contexts from the styler
  3756. removed: true, // whether it's inside the vis; true if removed (set true here since we call restore)
  3757. selected: params.selected ? true : false, // whether it's selected
  3758. selectable: params.selectable === undefined ? true : ( params.selectable ? true : false ), // whether it's selectable
  3759. locked: params.locked ? true : false, // whether the element is locked (cannot be moved)
  3760. grabbed: false, // whether the element is grabbed by the mouse; renderer sets this privately
  3761. grabbable: params.grabbable === undefined ? true : ( params.grabbable ? true : false ), // whether the element can be grabbed
  3762. active: false, // whether the element is active from user interaction
  3763. classes: {}, // map ( className => true )
  3764. animation: { // object for currently-running animations
  3765. current: [],
  3766. queue: []
  3767. },
  3768. rscratch: {}, // object in which the renderer can store information
  3769. scratch: {}, // scratch objects
  3770. edges: [], // array of connected edges
  3771. children: [] // array of children
  3772. };
  3773. // renderedPosition overrides if specified
  3774. if( params.renderedPosition ){
  3775. var rpos = params.renderedPosition;
  3776. var pan = cy.pan();
  3777. var zoom = cy.zoom();
  3778. this._private.position = {
  3779. x: (rpos.x - pan.x)/zoom,
  3780. y: (rpos.y - pan.y)/zoom
  3781. };
  3782. }
  3783. if( $$.is.string(params.classes) ){
  3784. var classes = params.classes.split(/\s+/);
  3785. for( var i = 0, l = classes.length; i < l; i++ ){
  3786. var cls = classes[i];
  3787. if( !cls || cls === "" ){ continue; }
  3788. self._private.classes[cls] = true;
  3789. }
  3790. }
  3791. if( restore === undefined || restore ){
  3792. this.restore();
  3793. }
  3794. };
  3795. // Collection
  3796. ////////////////////////////////////////////////////////////////////////////////////////////////////
  3797. // represents a set of nodes, edges, or both together
  3798. $$.Collection = function(cy, elements){
  3799. if( !(this instanceof $$.Collection) ){
  3800. return new $$.Collection(cy, elements);
  3801. }
  3802. if( cy === undefined || !$$.is.core(cy) ){
  3803. $$.util.error("A collection must have a reference to the core");
  3804. return;
  3805. }
  3806. var ids = {};
  3807. var uniqueElements = [];
  3808. var createdElements = false;
  3809. if( !elements ){
  3810. elements = [];
  3811. } else if( elements.length > 0 && $$.is.plainObject( elements[0] ) && !$$.is.element( elements[0] ) ){
  3812. createdElements = true;
  3813. // make elements from json and restore all at once later
  3814. var eles = [];
  3815. var elesIds = {};
  3816. for( var i = 0, l = elements.length; i < l; i++ ){
  3817. var json = elements[i];
  3818. if( json.data == null ){
  3819. json.data = {};
  3820. }
  3821. var data = json.data;
  3822. // make sure newly created elements have valid ids
  3823. if( data.id == null ){
  3824. data.id = idFactory.generate( cy, json );
  3825. } else if( cy.getElementById( data.id ).length != 0 || elesIds[ data.id ] ){
  3826. continue; // can't create element
  3827. }
  3828. var ele = new $$.Element( cy, json, false );
  3829. eles.push( ele );
  3830. elesIds[ data.id ] = true;
  3831. }
  3832. elements = eles;
  3833. }
  3834. for( var i = 0, l = elements.length; i < l; i++ ){
  3835. var element = elements[i];
  3836. if( !element ){ continue; }
  3837. var id = element._private.data.id;
  3838. if( !ids[ id ] ){
  3839. ids[ id ] = element;
  3840. uniqueElements.push( element );
  3841. }
  3842. }
  3843. for(var i = 0, l = uniqueElements.length; i < l; i++){
  3844. this[i] = uniqueElements[i];
  3845. }
  3846. this.length = uniqueElements.length;
  3847. this._private = {
  3848. cy: cy,
  3849. ids: ids
  3850. };
  3851. // restore the elements if we created them from json
  3852. if( createdElements ){
  3853. this.restore();
  3854. }
  3855. };
  3856. // Functions
  3857. ////////////////////////////////////////////////////////////////////////////////////////////////////
  3858. // keep the prototypes in sync (an element has the same functions as a collection)
  3859. // and use $$.elefn and $$.elesfn as shorthands to the prototypes
  3860. $$.elefn = $$.elesfn = $$.Element.prototype = $$.Collection.prototype;
  3861. $$.elesfn.cy = function(){
  3862. return this._private.cy;
  3863. };
  3864. $$.elesfn.element = function(){
  3865. return this[0];
  3866. };
  3867. $$.elesfn.collection = function(){
  3868. if( $$.is.collection(this) ){
  3869. return this;
  3870. } else { // an element
  3871. return new $$.Collection( this._private.cy, [this] );
  3872. }
  3873. };
  3874. $$.elesfn.json = function(){
  3875. var ele = this.element();
  3876. if( ele == null ){ return undefined }
  3877. var p = ele._private;
  3878. var json = $$.util.copy({
  3879. data: p.data,
  3880. position: p.position,
  3881. group: p.group,
  3882. bypass: p.bypass,
  3883. removed: p.removed,
  3884. selected: p.selected,
  3885. selectable: p.selectable,
  3886. locked: p.locked,
  3887. grabbed: p.grabbed,
  3888. grabbable: p.grabbable,
  3889. classes: ""
  3890. });
  3891. var classes = [];
  3892. for( var cls in p.classes ){
  3893. classes.push(cls);
  3894. }
  3895. for( var i = 0; i < classes.length; i++ ){
  3896. var cls = classes[i];
  3897. json.classes += cls + ( i < classes.length - 1 ? " " : "" );
  3898. }
  3899. return json;
  3900. };
  3901. $$.elesfn.restore = function( notifyRenderer ){
  3902. var self = this;
  3903. var restored = [];
  3904. var cy = self.cy();
  3905. if( notifyRenderer === undefined ){
  3906. notifyRenderer = true;
  3907. }
  3908. // create arrays of nodes and edges, since we need to
  3909. // restore the nodes first
  3910. var elements = [];
  3911. var nodes = [], edges = [];
  3912. var numNodes = 0;
  3913. var numEdges = 0;
  3914. for( var i = 0, l = self.length; i < l; i++ ){
  3915. var ele = self[i];
  3916. // keep nodes first in the array and edges after
  3917. if( ele.isNode() ){ // put to front of array if node
  3918. nodes.push( ele );
  3919. numNodes++;
  3920. } else { // put to end of array if edge
  3921. edges.push( ele );
  3922. numEdges++;
  3923. }
  3924. }
  3925. elements = nodes.concat( edges );
  3926. // now, restore each element
  3927. for( var i = 0, l = elements.length; i < l; i++ ){
  3928. var ele = elements[i];
  3929. if( !ele.removed() ){
  3930. // don't need to do anything
  3931. continue;
  3932. }
  3933. var _private = ele._private;
  3934. var data = _private.data;
  3935. // set id and validate
  3936. if( data.id === undefined ){
  3937. data.id = idFactory.generate( cy, ele );
  3938. } else if( $$.is.emptyString(data.id) || !$$.is.string(data.id) ){
  3939. // can't create element if it has empty string as id or non-string id
  3940. continue;
  3941. } else if( cy.getElementById( data.id ).length != 0 ){
  3942. // can't create element if one already has that id
  3943. continue;
  3944. }
  3945. var id = data.id; // id is finalised, now let's keep a ref
  3946. if( ele.isEdge() ){ // extra checks for edges
  3947. var edge = ele;
  3948. var fields = ["source", "target"];
  3949. var fieldsLength = fields.length;
  3950. for(var j = 0; j < fieldsLength; j++){
  3951. var field = fields[j];
  3952. var val = data[field];
  3953. if( val == null || val === "" ){
  3954. // can't create if source or target is not defined properly
  3955. continue;
  3956. } else if( cy.getElementById(val).empty() ){
  3957. // can't create edge if one of its nodes doesn't exist
  3958. continue;
  3959. }
  3960. }
  3961. var src = cy.getElementById( data.source );
  3962. var tgt = cy.getElementById( data.target );
  3963. src._private.edges.push( edge );
  3964. tgt._private.edges.push( edge );
  3965. } // if is edge
  3966. // create mock ids map for element so it can be used like collections
  3967. _private.ids = {};
  3968. _private.ids[ data.id ] = ele;
  3969. _private.removed = false;
  3970. cy.addToPool( ele );
  3971. restored.push( ele );
  3972. } // for each element
  3973. // do compound node sanity checks
  3974. for( var i = 0; i < numNodes; i++ ){ // each node
  3975. var node = elements[i];
  3976. var data = node._private.data;
  3977. var id = data.id;
  3978. var parentId = node._private.data.parent;
  3979. var specifiedParent = parentId != null;
  3980. if( specifiedParent ){
  3981. var parent = cy.getElementById( parentId );
  3982. if( parent.empty() ){
  3983. // non-existant parent; just remove it
  3984. delete data.parent;
  3985. } else {
  3986. var selfAsParent = false;
  3987. var ancestor = parent;
  3988. while( !ancestor.empty() ){
  3989. if( node.same(ancestor) ){
  3990. // mark self as parent and remove from data
  3991. selfAsParent = true;
  3992. delete data.parent; // remove parent reference
  3993. // exit or we loop forever
  3994. break;
  3995. }
  3996. ancestor = ancestor.parent();
  3997. }
  3998. if( !selfAsParent ){
  3999. // connect with children
  4000. parent[0]._private.children.push( node );
  4001. // let the core know we have a compound graph
  4002. cy._private.hasCompoundNodes = true;
  4003. }
  4004. } // else
  4005. } // if specified parent
  4006. } // for each node
  4007. restored = new $$.Collection( cy, restored );
  4008. if( restored.length > 0 ){
  4009. var toUpdateStyle = restored.add( restored.connectedNodes() ).add( restored.parent() );
  4010. toUpdateStyle.updateStyle( notifyRenderer );
  4011. if( notifyRenderer ){
  4012. restored.rtrigger("add");
  4013. } else {
  4014. restored.trigger("add");
  4015. }
  4016. }
  4017. return self; // chainability
  4018. };
  4019. $$.elesfn.removed = function(){
  4020. var ele = this[0];
  4021. return ele && ele._private.removed;
  4022. };
  4023. $$.elesfn.inside = function(){
  4024. var ele = this[0];
  4025. return ele && !ele._private.removed;
  4026. };
  4027. $$.elesfn.remove = function( notifyRenderer ){
  4028. var self = this;
  4029. var removed = [];
  4030. var elesToRemove = [];
  4031. var elesToRemoveIds = {};
  4032. var cy = self._private.cy;
  4033. if( notifyRenderer === undefined ){
  4034. notifyRenderer = true;
  4035. }
  4036. // add connected edges
  4037. function addConnectedEdges(node){
  4038. var edges = node._private.edges;
  4039. for( var i = 0; i < edges.length; i++ ){
  4040. add( edges[i] );
  4041. }
  4042. }
  4043. // add descendant nodes
  4044. function addChildren(node){
  4045. var children = node._private.children;
  4046. for( var i = 0; i < children.length; i++ ){
  4047. add( children[i] );
  4048. }
  4049. }
  4050. function add( ele ){
  4051. var alreadyAdded = elesToRemoveIds[ ele.id() ];
  4052. if( alreadyAdded ){
  4053. return;
  4054. } else {
  4055. elesToRemoveIds[ ele.id() ] = true;
  4056. }
  4057. if( ele.isNode() ){
  4058. elesToRemove.push( ele ); // nodes are removed last
  4059. addConnectedEdges( ele );
  4060. addChildren( ele );
  4061. } else {
  4062. elesToRemove.unshift( ele ); // edges are removed first
  4063. }
  4064. }
  4065. // make the list of elements to remove
  4066. // (may be removing more than specified due to connected edges etc)
  4067. for( var i = 0, l = self.length; i < l; i++ ){
  4068. var ele = self[i];
  4069. add( ele );
  4070. }
  4071. function removeEdgeRef(node, edge){
  4072. var connectedEdges = node._private.edges;
  4073. for( var j = 0; j < connectedEdges.length; j++ ){
  4074. var connectedEdge = connectedEdges[j];
  4075. if( edge === connectedEdge ){
  4076. connectedEdges.splice( j, 1 );
  4077. break;
  4078. }
  4079. }
  4080. }
  4081. function removeChildRef(parent, ele){
  4082. ele = ele[0];
  4083. parent = parent[0];
  4084. var children = parent._private.children;
  4085. for( var j = 0; j < children.length; j++ ){
  4086. if( children[j][0] === ele[0] ){
  4087. children.splice(j, 1);
  4088. break;
  4089. }
  4090. }
  4091. }
  4092. for( var i = 0; i < elesToRemove.length; i++ ){
  4093. var ele = elesToRemove[i];
  4094. // mark as removed
  4095. ele._private.removed = true;
  4096. // remove from core pool
  4097. cy.removeFromPool( ele );
  4098. // add to list of removed elements
  4099. removed.push( ele );
  4100. if( ele.isEdge() ){ // remove references to this edge in its connected nodes
  4101. var src = ele.source()[0];
  4102. var tgt = ele.target()[0];
  4103. removeEdgeRef( src, ele );
  4104. removeEdgeRef( tgt, ele );
  4105. } else { // remove reference to parent
  4106. var parent = ele.parent();
  4107. if( parent.length !== 0 ){
  4108. removeChildRef(parent, ele);
  4109. }
  4110. }
  4111. }
  4112. // check to see if we have a compound graph or not
  4113. var elesStillInside = cy._private.elements;
  4114. cy._private.hasCompoundNodes = false;
  4115. for( var i = 0; i < elesStillInside.length; i++ ){
  4116. var ele = elesStillInside[i];
  4117. if( ele.isParent() ){
  4118. cy._private.hasCompoundNodes = true;
  4119. break;
  4120. }
  4121. }
  4122. var removedElements = new $$.Collection( this.cy(), removed );
  4123. if( removedElements.size() > 0 ){
  4124. // must manually notify since trigger won't do this automatically once removed
  4125. if( notifyRenderer ){
  4126. this.cy().notify({
  4127. type: "remove",
  4128. collection: removedElements
  4129. });
  4130. }
  4131. removedElements.trigger("remove");
  4132. }
  4133. // check for empty remaining parent nodes
  4134. var checkedParentId = {};
  4135. for( var i = 0; i < elesToRemove.length; i++ ){
  4136. var ele = elesToRemove[i];
  4137. var isNode = ele._private.group === "nodes";
  4138. var parentId = ele._private.data.parent;
  4139. if( isNode && parentId !== undefined && !checkedParentId[ parentId ] ){
  4140. checkedParentId[ parentId ] = true;
  4141. var parent = cy.getElementById( parentId );
  4142. if( parent && parent.length !== 0 && !parent._private.removed && parent.children().length === 0 ){
  4143. parent.updateStyle();
  4144. }
  4145. }
  4146. }
  4147. return this;
  4148. };
  4149. })( cytoscape );
  4150. ;(function( $$ ){
  4151. $$.fn.eles({
  4152. animated: function(){
  4153. var ele = this[0];
  4154. if( ele ){
  4155. return ele._private.animation.current.length > 0;
  4156. }
  4157. },
  4158. clearQueue: function(){
  4159. for( var i = 0; i < this.length; i++ ){
  4160. var ele = this[i];
  4161. ele._private.animation.queue = [];
  4162. }
  4163. return this;
  4164. },
  4165. delay: function( time, complete ){
  4166. this.animate({
  4167. delay: time
  4168. }, {
  4169. duration: time,
  4170. complete: complete
  4171. });
  4172. return this;
  4173. },
  4174. animate: function( properties, params ){
  4175. var callTime = +new Date;
  4176. var cy = this._private.cy;
  4177. var style = cy.style();
  4178. var q;
  4179. if( params === undefined ){
  4180. params = {};
  4181. }
  4182. if( params.duration === undefined ){
  4183. params.duration = 400;
  4184. }
  4185. switch( params.duration ){
  4186. case "slow":
  4187. params.duration = 600;
  4188. break;
  4189. case "fast":
  4190. params.duration = 200;
  4191. break;
  4192. }
  4193. if( properties == null || (properties.position == null && properties.css == null && properties.delay == null) ){
  4194. return this; // nothing to animate
  4195. }
  4196. if( properties.css ){
  4197. properties.css = style.getValueStyle( properties.css );
  4198. }
  4199. for( var i = 0; i < this.length; i++ ){
  4200. var self = this[i];
  4201. var pos = self._private.position;
  4202. var startPosition = {
  4203. x: pos.x,
  4204. y: pos.y
  4205. };
  4206. var startStyle = style.getValueStyle( self );
  4207. if( self.animated() && (params.queue === undefined || params.queue) ){
  4208. q = self._private.animation.queue;
  4209. } else {
  4210. q = self._private.animation.current;
  4211. }
  4212. q.push({
  4213. properties: properties,
  4214. duration: params.duration,
  4215. params: params,
  4216. callTime: callTime,
  4217. startPosition: startPosition,
  4218. startStyle: startStyle
  4219. });
  4220. }
  4221. cy.addToAnimationPool( this );
  4222. return this; // chaining
  4223. }, // animate
  4224. stop: function(clearQueue, jumpToEnd){
  4225. for( var i = 0; i < this.length; i++ ){
  4226. var self = this[i];
  4227. var anis = self._private.animation.current;
  4228. for( var j = 0; j < anis.length; j++ ){
  4229. var animation = anis[j];
  4230. if( jumpToEnd ){
  4231. // next iteration of the animation loop, the animation
  4232. // will go straight to the end and be removed
  4233. animation.duration = 0;
  4234. }
  4235. }
  4236. // clear the queue of future animations
  4237. if( clearQueue ){
  4238. self._private.animation.queue = [];
  4239. }
  4240. }
  4241. // we have to notify (the animation loop doesn't do it for us on `stop`)
  4242. this.cy().notify({
  4243. collection: this,
  4244. type: "draw"
  4245. });
  4246. return this;
  4247. }
  4248. });
  4249. })( cytoscape );
  4250. ;(function( $$ ){
  4251. $$.fn.eles({
  4252. addClass: function(classes){
  4253. classes = classes.split(/\s+/);
  4254. var self = this;
  4255. var changed = [];
  4256. for( var i = 0; i < classes.length; i++ ){
  4257. var cls = classes[i];
  4258. if( $$.is.emptyString(cls) ){ continue; }
  4259. for( var j = 0; j < self.length; j++ ){
  4260. var ele = self[j];
  4261. var hasClass = ele._private.classes[cls];
  4262. ele._private.classes[cls] = true;
  4263. if( !hasClass ){ // if didn't already have, add to list of changed
  4264. changed.push( ele );
  4265. }
  4266. }
  4267. }
  4268. // trigger update style on those eles that had class changes
  4269. if( changed.length > 0 ){
  4270. new $$.Collection(this._private.cy, changed).updateStyle();
  4271. }
  4272. self.trigger("class");
  4273. return self;
  4274. },
  4275. hasClass: function(className){
  4276. var ele = this[0];
  4277. return ele != null && ele._private.classes[className];
  4278. },
  4279. toggleClass: function(classesStr, toggle){
  4280. var classes = classesStr.split(/\s+/);
  4281. var self = this;
  4282. var changed = []; // eles who had classes changed
  4283. for( var i = 0, il = self.length; i < il; i++ ){
  4284. var ele = self[i];
  4285. for( var j = 0; j < classes.length; j++ ){
  4286. var cls = classes[j];
  4287. if( $$.is.emptyString(cls) ){ continue; }
  4288. var hasClass = ele._private.classes[cls];
  4289. var shouldAdd = toggle || (toggle === undefined && !hasClass);
  4290. if( shouldAdd ){
  4291. ele._private.classes[cls] = true;
  4292. if( !hasClass ){ changed.push(ele); }
  4293. } else { // then remove
  4294. ele._private.classes[cls] = false;
  4295. if( hasClass ){ changed.push(ele); }
  4296. }
  4297. } // for j classes
  4298. } // for i eles
  4299. // trigger update style on those eles that had class changes
  4300. if( changed.length > 0 ){
  4301. new $$.Collection(this._private.cy, changed).updateStyle();
  4302. }
  4303. self.trigger("class");
  4304. return self;
  4305. },
  4306. removeClass: function(classes){
  4307. classes = classes.split(/\s+/);
  4308. var self = this;
  4309. var changed = [];
  4310. for( var i = 0; i < self.length; i++ ){
  4311. var ele = self[i];
  4312. for( var j = 0; j < classes.length; j++ ){
  4313. var cls = classes[j];
  4314. if( !cls || cls === "" ){ continue; }
  4315. var hasClass = ele._private.classes[cls];
  4316. delete ele._private.classes[cls];
  4317. if( hasClass ){ // then we changed its set of classes
  4318. changed.push( ele );
  4319. }
  4320. }
  4321. }
  4322. // trigger update style on those eles that had class changes
  4323. if( changed.length > 0 ){
  4324. new $$.Collection(self._private.cy, changed).updateStyle();
  4325. }
  4326. self.trigger("class");
  4327. return self;
  4328. }
  4329. });
  4330. })( cytoscape );
  4331. ;(function($$){
  4332. $$.fn.eles({
  4333. allAre: function(selector){
  4334. return this.filter(selector).length === this.length;
  4335. },
  4336. is: function(selector){
  4337. return this.filter(selector).length > 0;
  4338. },
  4339. same: function( collection ){
  4340. collection = this.cy().collection( collection );
  4341. // cheap extra check
  4342. if( this.length !== collection.length ){
  4343. return false;
  4344. }
  4345. return this.intersect( collection ).length === this.length;
  4346. },
  4347. anySame: function(collection){
  4348. collection = this.cy().collection( collection );
  4349. return this.intersect( collection ).length > 0;
  4350. },
  4351. allAreNeighbors: function(collection){
  4352. collection = this.cy().collection( collection );
  4353. return this.neighborhood().intersect( collection ).length === collection.length;
  4354. }
  4355. });
  4356. })( cytoscape );
  4357. ;(function($$){
  4358. var borderWidthMultiplier = 1.4;
  4359. var borderWidthAdjustment = 1;
  4360. $$.fn.eles({
  4361. // fully updates (recalculates) the style for the elements
  4362. updateStyle: function( notifyRenderer ){
  4363. var cy = this._private.cy;
  4364. var style = cy.style();
  4365. notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;
  4366. style.apply( this );
  4367. if( notifyRenderer ){
  4368. this.rtrigger("style"); // let renderer know we changed style
  4369. } else {
  4370. this.trigger("style"); // just fire the event
  4371. }
  4372. return this; // chaining
  4373. },
  4374. // just update the mappers in the elements' styles; cheaper than eles.updateStyle()
  4375. updateMappers: function( notifyRenderer ){
  4376. var cy = this._private.cy;
  4377. var style = cy.style();
  4378. notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;
  4379. for( var i = 0; i < this.length; i++ ){
  4380. var ele = this[i];
  4381. style.apply( ele );
  4382. }
  4383. if( notifyRenderer ){
  4384. this.rtrigger("style"); // let renderer know we changed style
  4385. } else {
  4386. this.trigger("style"); // just fire the event
  4387. }
  4388. return this; // chaining
  4389. },
  4390. data: $$.define.data({
  4391. field: "data",
  4392. bindingEvent: "data",
  4393. allowBinding: true,
  4394. allowSetting: true,
  4395. settingEvent: "data",
  4396. settingTriggersEvent: true,
  4397. triggerFnName: "trigger",
  4398. allowGetting: true,
  4399. immutableKeys: {
  4400. "id": true,
  4401. "source": true,
  4402. "target": true,
  4403. "parent": true
  4404. },
  4405. updateMappers: true
  4406. }),
  4407. removeData: $$.define.removeData({
  4408. field: "data",
  4409. event: "data",
  4410. triggerFnName: "trigger",
  4411. triggerEvent: true,
  4412. immutableKeys: {
  4413. "id": true,
  4414. "source": true,
  4415. "target": true,
  4416. "parent": true
  4417. },
  4418. updateMappers: true
  4419. }),
  4420. batchData: $$.define.batchData({
  4421. field: "data",
  4422. event: "data",
  4423. triggerFnName: "trigger",
  4424. immutableKeys: {
  4425. "id": true,
  4426. "source": true,
  4427. "target": true,
  4428. "parent": true
  4429. },
  4430. updateMappers: true
  4431. }),
  4432. scratch: $$.define.data({
  4433. field: "scratch",
  4434. allowBinding: false,
  4435. allowSetting: true,
  4436. settingTriggersEvent: false,
  4437. allowGetting: true
  4438. }),
  4439. removeScratch: $$.define.removeData({
  4440. field: "scratch",
  4441. triggerEvent: false
  4442. }),
  4443. rscratch: $$.define.data({
  4444. field: "rscratch",
  4445. allowBinding: false,
  4446. allowSetting: true,
  4447. settingTriggersEvent: false,
  4448. allowGetting: true
  4449. }),
  4450. removeRscratch: $$.define.removeData({
  4451. field: "rscratch",
  4452. triggerEvent: false
  4453. }),
  4454. id: function(){
  4455. var ele = this[0];
  4456. if( ele ){
  4457. return ele._private.data.id;
  4458. }
  4459. },
  4460. position: $$.define.data({
  4461. field: "position",
  4462. bindingEvent: "position",
  4463. allowBinding: true,
  4464. allowSetting: true,
  4465. settingEvent: "position",
  4466. settingTriggersEvent: true,
  4467. triggerFnName: "rtrigger",
  4468. allowGetting: true,
  4469. validKeys: ["x", "y"]
  4470. }),
  4471. positions: function( pos ){
  4472. if( $$.is.plainObject(pos) ){
  4473. this.position(pos);
  4474. } else if( $$.is.fn(pos) ){
  4475. var fn = pos;
  4476. for( var i = 0; i < this.length; i++ ){
  4477. var ele = this[i];
  4478. var pos = fn.apply(ele, [i, ele]);
  4479. if( pos && !ele.locked() ){
  4480. var elePos = ele._private.position;
  4481. elePos.x = pos.x;
  4482. elePos.y = pos.y;
  4483. }
  4484. }
  4485. this.rtrigger("position");
  4486. }
  4487. return this; // chaining
  4488. },
  4489. // get the rendered (i.e. on screen) positon of the element
  4490. // TODO allow setting
  4491. renderedPosition: function( dim ){
  4492. var ele = this[0];
  4493. var cy = this.cy();
  4494. var zoom = cy.zoom();
  4495. var pan = cy.pan();
  4496. if( ele && ele.isNode() ){ // must have an element and must be a node to return position
  4497. var pos = ele._private.position;
  4498. var rpos = {
  4499. x: pos.x * zoom + pan.x,
  4500. y: pos.y * zoom + pan.y
  4501. };
  4502. if( dim === undefined ){ // then return the whole rendered position
  4503. return rpos;
  4504. } else { // then return the specified dimension
  4505. return rpos[ dim ];
  4506. }
  4507. }
  4508. },
  4509. // get the specified css property as a rendered value (i.e. on-screen value)
  4510. // or get the whole rendered style if no property specified (NB doesn't allow setting)
  4511. renderedCss: function( property ){
  4512. var ele = this[0];
  4513. if( ele ){
  4514. var renstyle = ele.cy().style().getRenderedStyle( ele );
  4515. if( property === undefined ){
  4516. return renstyle;
  4517. } else {
  4518. return renstyle[ property ];
  4519. }
  4520. }
  4521. },
  4522. // read the calculated css style of the element or override the style (via a bypass)
  4523. css: function( name, value ){
  4524. var style = this.cy().style();
  4525. if( $$.is.plainObject(name) ){ // then extend the bypass
  4526. var props = name;
  4527. style.applyBypass( this, props );
  4528. this.rtrigger("style"); // let the renderer know we've updated style
  4529. } else if( $$.is.string(name) ){
  4530. if( value === undefined ){ // then get the property from the style
  4531. var ele = this[0];
  4532. if( ele ){
  4533. return ele._private.style[ name ].strValue;
  4534. } else { // empty collection => can't get any value
  4535. return;
  4536. }
  4537. } else { // then set the bypass with the property value
  4538. style.applyBypass( this, name, value );
  4539. this.rtrigger("style"); // let the renderer know we've updated style
  4540. }
  4541. } else if( name === undefined ){
  4542. var ele = this[0];
  4543. if( ele ){
  4544. return style.getRawStyle( ele );
  4545. } else { // empty collection => can't get any value
  4546. return;
  4547. }
  4548. }
  4549. return this; // chaining
  4550. },
  4551. removeCss: function(){
  4552. var style = this.cy().style();
  4553. var eles = this;
  4554. for( var i = 0; i < eles.length; i++ ){
  4555. var ele = eles[i];
  4556. style.removeAllBypasses( ele );
  4557. }
  4558. this.rtrigger('style');
  4559. },
  4560. show: function(){
  4561. this.css("visibility", "visible");
  4562. return this; // chaining
  4563. },
  4564. hide: function(){
  4565. this.css("visibility", "hidden");
  4566. return this; // chaining
  4567. },
  4568. visible: function(){
  4569. var ele = this[0];
  4570. if( ele ){
  4571. if( ele.css("visibility") !== "visible" ){
  4572. return false;
  4573. }
  4574. if( ele.isNode() ){
  4575. var parents = ele.parents();
  4576. for( var i = 0; i < parents.length; i++ ){
  4577. var parent = parents[i];
  4578. var parentVisibility = parent.css("visibility");
  4579. if( parentVisibility !== "visible" ){
  4580. return false;
  4581. }
  4582. }
  4583. return true;
  4584. } else if( ele.isEdge() ){
  4585. var src = ele.source();
  4586. var tgt = ele.target();
  4587. return src.visible() && tgt.visible();
  4588. }
  4589. }
  4590. },
  4591. hidden: function(){
  4592. var ele = this[0];
  4593. if( ele ){
  4594. return !this.visible();
  4595. }
  4596. },
  4597. // convenience function to get a numerical value for the width of the node/edge
  4598. width: function(){
  4599. var ele = this[0];
  4600. if( ele ){
  4601. var w = this._private.style.width;
  4602. return w.strValue === "auto" ? ele._private.autoWidth : w.pxValue;
  4603. }
  4604. },
  4605. outerWidth: function(){
  4606. var ele = this[0];
  4607. if( ele ){
  4608. var style = this._private.style;
  4609. var width = style.width.strValue === "auto" ? ele._private.autoWidth : style.width.pxValue;;
  4610. var border = style["border-width"] ? style["border-width"].pxValue * borderWidthMultiplier + borderWidthAdjustment : 0;
  4611. return width + border;
  4612. }
  4613. },
  4614. renderedWidth: function(){
  4615. var ele = this[0];
  4616. if( ele ){
  4617. var width = this.width();
  4618. return width * this.cy().zoom();
  4619. }
  4620. },
  4621. renderedOuterWidth: function(){
  4622. var ele = this[0];
  4623. if( ele ){
  4624. var owidth = this.outerWidth();
  4625. return owidth * this.cy().zoom();
  4626. }
  4627. },
  4628. // convenience function to get a numerical value for the height of the node
  4629. height: function(){
  4630. var ele = this[0];
  4631. if( ele && ele.isNode() ){
  4632. var h = this._private.style.height;
  4633. return h.strValue === "auto" ? ele._private.autoHeight : h.pxValue;
  4634. }
  4635. },
  4636. outerHeight: function(){
  4637. var ele = this[0];
  4638. if( ele ){
  4639. var style = this._private.style;
  4640. var height = style.height.strValue === "auto" ? ele._private.autoHeight : style.height.pxValue;
  4641. var border = style["border-width"] ? style["border-width"].pxValue * borderWidthMultiplier + borderWidthAdjustment : 0;
  4642. return height + border;
  4643. }
  4644. },
  4645. renderedHeight: function(){
  4646. var ele = this[0];
  4647. if( ele ){
  4648. var height = this.height();
  4649. return height * this.cy().zoom();
  4650. }
  4651. },
  4652. renderedOuterHeight: function(){
  4653. var ele = this[0];
  4654. if( ele ){
  4655. var oheight = this.outerHeight();
  4656. return oheight * this.cy().zoom();
  4657. }
  4658. },
  4659. // get the position of the element relative to the container (i.e. not relative to parent node)
  4660. offset: function(){
  4661. var ele = this[0];
  4662. if( ele && ele.isNode() ){
  4663. var offset = {
  4664. x: ele._private.position.x,
  4665. y: ele._private.position.y
  4666. };
  4667. var parents = ele.parents();
  4668. for( var i = 0; i < parents.length; i++ ){
  4669. var parent = parents[i];
  4670. var parentPos = parent._private.position;
  4671. offset.x += parentPos.x;
  4672. offset.y += parentPos.y;
  4673. }
  4674. return offset;
  4675. }
  4676. },
  4677. renderedOffset: function(){
  4678. var ele = this[0];
  4679. if( ele && ele.isNode() ){
  4680. var offset = this.offset();
  4681. var cy = this.cy();
  4682. var zoom = cy.zoom();
  4683. var pan = cy.pan();
  4684. return {
  4685. x: offset.x * zoom + pan.x,
  4686. y: offset.y * zoom + pan.y
  4687. };
  4688. }
  4689. },
  4690. // get the bounding box of the elements (in raw model position)
  4691. boundingBox: function( selector ){
  4692. var eles = this;
  4693. if( !selector || ( $$.is.elementOrCollection(selector) && selector.length === 0 ) ){
  4694. eles = this;
  4695. } else if( $$.is.string(selector) ){
  4696. eles = this.filter( selector );
  4697. } else if( $$.is.elementOrCollection(selector) ){
  4698. eles = selector;
  4699. }
  4700. var x1 = Infinity;
  4701. var x2 = -Infinity;
  4702. var y1 = Infinity;
  4703. var y2 = -Infinity;
  4704. // find bounds of elements
  4705. for( var i = 0; i < eles.length; i++ ){
  4706. var ele = eles[i];
  4707. var ex1, ex2, ey1, ey2, x, y;
  4708. if( ele.isNode() ){
  4709. var pos = ele._private.position;
  4710. x = pos.x;
  4711. y = pos.y;
  4712. var w = ele.outerWidth();
  4713. var halfW = w/2;
  4714. var h = ele.outerHeight();
  4715. var halfH = h/2;
  4716. // handle node dimensions
  4717. /////////////////////////
  4718. ex1 = x - halfW;
  4719. ex2 = x + halfW;
  4720. ey1 = y - halfH;
  4721. ey2 = y + halfH;
  4722. x1 = ex1 < x1 ? ex1 : x1;
  4723. x2 = ex2 > x2 ? ex2 : x2;
  4724. y1 = ey1 < y1 ? ey1 : y1;
  4725. y2 = ey2 > y2 ? ey2 : y2;
  4726. } else { // is edge
  4727. var n1pos = ele.source()[0]._private.position;
  4728. var n2pos = ele.target()[0]._private.position;
  4729. // handle edge dimensions (rough box estimate)
  4730. //////////////////////////////////////////////
  4731. var rstyle = ele._private.rstyle;
  4732. x = rstyle.labelX;
  4733. y = rstyle.labelY;
  4734. ex1 = n1pos.x;
  4735. ex2 = n2pos.x;
  4736. ey1 = n1pos.y;
  4737. ey2 = n2pos.y;
  4738. if( ex1 > ex2 ){
  4739. var temp = ex1;
  4740. ex1 = ex2;
  4741. ex2 = temp;
  4742. }
  4743. if( ey1 > ey2 ){
  4744. var temp = ey1;
  4745. ey1 = ey2;
  4746. ey2 = temp;
  4747. }
  4748. x1 = ex1 < x1 ? ex1 : x1;
  4749. x2 = ex2 > x2 ? ex2 : x2;
  4750. y1 = ey1 < y1 ? ey1 : y1;
  4751. y2 = ey2 > y2 ? ey2 : y2;
  4752. // handle points along edge (sanity check)
  4753. //////////////////////////////////////////
  4754. var bpts = rstyle.bezierPts || [];
  4755. var w = ele._private.style['width'].value;
  4756. for( var j = 0; j < bpts.length; j++ ){
  4757. var bpt = bpts[j];
  4758. x1 = bpt.x - w < x1 ? bpt.x - w : x1;
  4759. x2 = bpt.x + w > x2 ? bpt.x + w : x2;
  4760. y1 = bpt.y - w < y1 ? bpt.y - w : y1;
  4761. y2 = bpt.y + w > y2 ? bpt.y + w : y2;
  4762. }
  4763. }
  4764. // handle label dimensions
  4765. //////////////////////////
  4766. var style = ele._private.style;
  4767. var label = style['content'].value;
  4768. var fontSize = style['font-size'];
  4769. var halign = style['text-halign'];
  4770. var valign = style['text-valign'];
  4771. var labelWidth = ele._private.rstyle.labelWidth;
  4772. if( label && fontSize && labelWidth != undefined && halign && valign ){
  4773. var lh = fontSize.value;
  4774. var lw = labelWidth;
  4775. var lx1, lx2, ly1, ly2;
  4776. switch( halign.value ){
  4777. case "left":
  4778. lx1 = ex1 - lw;
  4779. lx2 = ex1;
  4780. break;
  4781. case "center":
  4782. lx1 = x - lw/2;
  4783. lx2 = x + lw/2;
  4784. break;
  4785. case "right":
  4786. lx1 = ex2;
  4787. lx2 = ex2 + lw;
  4788. break;
  4789. }
  4790. if( ele.isEdge() ){ // force center case
  4791. lx1 = x - lw/2;
  4792. lx2 = x + lw/2;
  4793. }
  4794. switch( valign.value ){
  4795. case "top":
  4796. ly1 = ey1 - lh;
  4797. ly2 = ey1;
  4798. break;
  4799. case "center":
  4800. ly1 = y - lh/2;
  4801. ly2 = y + lh/2;
  4802. break;
  4803. case "bottom":
  4804. ly1 = ey2;
  4805. ly2 = ey2 + lh;
  4806. break;
  4807. }
  4808. if( ele.isEdge() ){ // force center case
  4809. ly1 = y - lh/2;
  4810. ly2 = y + lh/2;
  4811. }
  4812. x1 = lx1 < x1 ? lx1 : x1;
  4813. x2 = lx2 > x2 ? lx2 : x2;
  4814. y1 = ly1 < y1 ? ly1 : y1;
  4815. y2 = ly2 > y2 ? ly2 : y2;
  4816. }
  4817. } // for
  4818. // testing on debug page
  4819. // $('#bb').remove();
  4820. // $('#cytoscape').css('position', 'relative').append('<div id="bb"></div>');
  4821. // $('#bb').css({
  4822. // 'position': 'absolute',
  4823. // 'left': x1,
  4824. // 'top': y1,
  4825. // 'width': x2 - x1,
  4826. // 'height': y2 - y1,
  4827. // 'background': 'rgba(255, 0, 0, 0.5)'
  4828. // })
  4829. return {
  4830. x1: x1,
  4831. x2: x2,
  4832. y1: y1,
  4833. y2: y2,
  4834. w: x2 - x1,
  4835. h: y2 - y1
  4836. };
  4837. }
  4838. });
  4839. })( cytoscape );
  4840. ;(function( $$ ){
  4841. // Regular degree functions (works on single element)
  4842. ////////////////////////////////////////////////////////////////////////////////////////////////////
  4843. function defineDegreeFunction(callback){
  4844. return function(){
  4845. var self = this;
  4846. if( self.length === 0 ){ return; }
  4847. if( self.isNode() && !self.removed() ){
  4848. var degree = 0;
  4849. var node = self[0];
  4850. var connectedEdges = node._private.edges;
  4851. for( var i = 0; i < connectedEdges.length; i++ ){
  4852. var edge = connectedEdges[i];
  4853. degree += callback( node, edge );
  4854. }
  4855. return degree;
  4856. } else {
  4857. return;
  4858. }
  4859. };
  4860. }
  4861. $$.fn.eles({
  4862. degree: defineDegreeFunction(function(node, edge){
  4863. if( edge.source().same( edge.target() ) ){
  4864. return 2;
  4865. } else {
  4866. return 1;
  4867. }
  4868. }),
  4869. indegree: defineDegreeFunction(function(node, edge){
  4870. if( edge.target().same(node) ){
  4871. return 1;
  4872. } else {
  4873. return 0;
  4874. }
  4875. }),
  4876. outdegree: defineDegreeFunction(function(node, edge){
  4877. if( edge.source().same(node) ){
  4878. return 1;
  4879. } else {
  4880. return 0;
  4881. }
  4882. })
  4883. });
  4884. // Collection degree stats
  4885. ////////////////////////////////////////////////////////////////////////////////////////////////////
  4886. function defineDegreeBoundsFunction(degreeFn, callback){
  4887. return function(){
  4888. var ret = undefined;
  4889. var nodes = this.nodes();
  4890. for( var i = 0; i < nodes.length; i++ ){
  4891. var ele = nodes[i];
  4892. var degree = ele[degreeFn]();
  4893. if( degree !== undefined && (ret === undefined || callback(degree, ret)) ){
  4894. ret = degree;
  4895. }
  4896. }
  4897. return ret;
  4898. };
  4899. }
  4900. $$.fn.eles({
  4901. minDegree: defineDegreeBoundsFunction("degree", function(degree, min){
  4902. return degree < min;
  4903. }),
  4904. maxDegree: defineDegreeBoundsFunction("degree", function(degree, max){
  4905. return degree > max;
  4906. }),
  4907. minIndegree: defineDegreeBoundsFunction("indegree", function(degree, min){
  4908. return degree < min;
  4909. }),
  4910. maxIndegree: defineDegreeBoundsFunction("indegree", function(degree, max){
  4911. return degree > max;
  4912. }),
  4913. minOutdegree: defineDegreeBoundsFunction("outdegree", function(degree, min){
  4914. return degree < min;
  4915. }),
  4916. maxOutdegree: defineDegreeBoundsFunction("outdegree", function(degree, max){
  4917. return degree > max;
  4918. })
  4919. });
  4920. $$.fn.eles({
  4921. totalDegree: function(){
  4922. var total = 0;
  4923. var nodes = this.nodes();
  4924. for( var i = 0; i < nodes.length; i++ ){
  4925. total += nodes[i].degree();
  4926. }
  4927. return total;
  4928. }
  4929. });
  4930. })( cytoscape );
  4931. ;(function($$){
  4932. // Functions for binding & triggering events
  4933. ////////////////////////////////////////////////////////////////////////////////////////////////////
  4934. $$.fn.eles({
  4935. on: $$.define.on(), // .on( events [, selector] [, data], handler)
  4936. one: $$.define.on({ unbindSelfOnTrigger: true }),
  4937. once: $$.define.on({ unbindAllBindersOnTrigger: true }),
  4938. off: $$.define.off(), // .off( events [, selector] [, handler] )
  4939. trigger: $$.define.trigger(), // .trigger( events [, extraParams] )
  4940. rtrigger: function(event, extraParams){ // for internal use only
  4941. // notify renderer unless removed
  4942. this.cy().notify({
  4943. type: event,
  4944. collection: this.filter(function(){
  4945. return !this.removed();
  4946. })
  4947. });
  4948. this.trigger(event, extraParams);
  4949. return this;
  4950. }
  4951. });
  4952. // aliases for those folks who like old stuff:
  4953. $$.elesfn.bind = $$.elesfn.on;
  4954. $$.elesfn.unbind = $$.elesfn.off;
  4955. // add event aliases like .click()
  4956. $$.define.event.aliasesOn( $$.elesfn );
  4957. })( cytoscape );
  4958. ;(function($$){
  4959. $$.fn.eles({
  4960. isNode: function(){
  4961. return this.group() === "nodes";
  4962. },
  4963. isEdge: function(){
  4964. return this.group() === "edges";
  4965. },
  4966. isLoop: function(){
  4967. return this.isEdge() && this.source().id() === this.target().id();
  4968. },
  4969. group: function(){
  4970. var ele = this[0];
  4971. if( ele ){
  4972. return ele._private.group;
  4973. }
  4974. }
  4975. });
  4976. })( cytoscape );
  4977. ;(function($$){
  4978. // Functions for iterating over collections
  4979. ////////////////////////////////////////////////////////////////////////////////////////////////////
  4980. $$.fn.eles({
  4981. each: function(fn){
  4982. if( $$.is.fn(fn) ){
  4983. for(var i = 0; i < this.length; i++){
  4984. var ele = this[i];
  4985. var ret = fn.apply( ele, [ i, ele ] );
  4986. if( ret === false ){ break; } // exit each early on return false
  4987. }
  4988. }
  4989. return this;
  4990. },
  4991. toArray: function(){
  4992. var array = [];
  4993. for(var i = 0; i < this.length; i++){
  4994. array.push( this[i] );
  4995. }
  4996. return array;
  4997. },
  4998. slice: function(start, end){
  4999. var array = [];
  5000. var thisSize = this.length;
  5001. if( end == null ){
  5002. end = thisSize;
  5003. }
  5004. if( start < 0 ){
  5005. start = thisSize + start;
  5006. }
  5007. for(var i = start; i >= 0 && i < end && i < thisSize; i++){
  5008. array.push( this[i] );
  5009. }
  5010. return new $$.Collection(this.cy(), array);
  5011. },
  5012. size: function(){
  5013. return this.length;
  5014. },
  5015. eq: function(i){
  5016. return this[i];
  5017. },
  5018. empty: function(){
  5019. return this.length === 0;
  5020. },
  5021. nonempty: function(){
  5022. return !this.empty();
  5023. }
  5024. });
  5025. })( cytoscape );
  5026. ;(function($$){
  5027. // Collection functions that toggle a boolean value
  5028. ////////////////////////////////////////////////////////////////////////////////////////////////////
  5029. function defineSwitchFunction(params){
  5030. return function(){
  5031. var args = arguments;
  5032. // e.g. cy.nodes().select( data, handler )
  5033. if( args.length === 2 ){
  5034. var data = args[0];
  5035. var handler = args[1];
  5036. this.bind( params.event, data, handler );
  5037. }
  5038. // e.g. cy.nodes().select( handler )
  5039. else if( args.length === 1 ){
  5040. var handler = args[0];
  5041. this.bind( params.event, handler );
  5042. }
  5043. // e.g. cy.nodes().select()
  5044. else if( args.length === 0 ){
  5045. for( var i = 0; i < this.length; i++ ){
  5046. var ele = this[i];
  5047. if( !params.ableField || ele._private[params.ableField] ){
  5048. ele._private[params.field] = params.value;
  5049. }
  5050. }
  5051. this.updateStyle(); // change of state => possible change of style
  5052. this.trigger(params.event);
  5053. }
  5054. return this;
  5055. };
  5056. }
  5057. function defineSwitchSet( params ){
  5058. $$.elesfn[ params.field ] = function(){
  5059. var ele = this[0];
  5060. if( ele ){
  5061. return ele._private[ params.field ];
  5062. }
  5063. };
  5064. $$.elesfn[ params.on ] = defineSwitchFunction({
  5065. event: params.on,
  5066. field: params.field,
  5067. ableField: params.ableField,
  5068. value: true
  5069. });
  5070. $$.elesfn[ params.off ] = defineSwitchFunction({
  5071. event: params.off,
  5072. field: params.field,
  5073. ableField: params.ableField,
  5074. value: false
  5075. });
  5076. }
  5077. defineSwitchSet({
  5078. field: "locked",
  5079. on: "lock",
  5080. off: "unlock"
  5081. });
  5082. defineSwitchSet({
  5083. field: "grabbable",
  5084. on: "grabify",
  5085. off: "ungrabify"
  5086. });
  5087. defineSwitchSet({
  5088. field: "selected",
  5089. ableField: "selectable",
  5090. on: "select",
  5091. off: "unselect"
  5092. });
  5093. defineSwitchSet({
  5094. field: "selectable",
  5095. on: "selectify",
  5096. off: "unselectify"
  5097. });
  5098. $$.elesfn.grabbed = function(){
  5099. var ele = this[0];
  5100. if( ele ){
  5101. return ele._private.grabbed;
  5102. }
  5103. };
  5104. defineSwitchSet({
  5105. field: "active",
  5106. on: "activate",
  5107. off: "unactivate"
  5108. });
  5109. $$.elesfn.inactive = function(){
  5110. var ele = this[0];
  5111. if( ele ){
  5112. return !ele._private.active;
  5113. }
  5114. };
  5115. })( cytoscape );
  5116. ;(function($$){
  5117. $$.fn.eles({
  5118. nodes: function(selector){
  5119. return this.filter(function(i, element){
  5120. return element.isNode();
  5121. }).filter(selector);
  5122. },
  5123. edges: function(selector){
  5124. return this.filter(function(i, element){
  5125. return element.isEdge();
  5126. }).filter(selector);
  5127. },
  5128. filter: function(filter){
  5129. var cy = this._private.cy;
  5130. if( $$.is.fn(filter) ){
  5131. var elements = [];
  5132. for( var i = 0; i < this.length; i++ ){
  5133. var ele = this[i];
  5134. if( filter.apply(ele, [i, ele]) ){
  5135. elements.push(ele);
  5136. }
  5137. }
  5138. return new $$.Collection(cy, elements);
  5139. } else if( $$.is.string(filter) || $$.is.elementOrCollection(filter) ){
  5140. return new $$.Selector(filter).filter(this);
  5141. } else if( filter === undefined ){
  5142. return this;
  5143. }
  5144. return new $$.Collection( cy ); // if not handled by above, give 'em an empty collection
  5145. },
  5146. not: function(toRemove){
  5147. var cy = this._private.cy;
  5148. if( !toRemove ){
  5149. return this;
  5150. } else {
  5151. if( $$.is.string( toRemove ) ){
  5152. toRemove = this.filter( toRemove );
  5153. }
  5154. var elements = [];
  5155. for( var i = 0; i < this.length; i++ ){
  5156. var element = this[i];
  5157. var remove = toRemove._private.ids[ element.id() ];
  5158. if( !remove ){
  5159. elements.push( element );
  5160. }
  5161. }
  5162. return new $$.Collection( cy, elements );
  5163. }
  5164. },
  5165. intersect: function( other ){
  5166. var self = this;
  5167. var cy = this._private.cy;
  5168. // if a selector is specified, then filter by it
  5169. if( $$.is.string(other) ){
  5170. var selector = other;
  5171. return this.filter( selector );
  5172. }
  5173. var elements = [];
  5174. var col1 = this;
  5175. var col2 = other;
  5176. var col1Smaller = this.length < other.length;
  5177. var ids1 = col1Smaller ? col1._private.ids : col2._private.ids;
  5178. var ids2 = col1Smaller ? col2._private.ids : col1._private.ids;
  5179. for( var id in ids1 ){
  5180. var ele = ids2[ id ];
  5181. if( ele ){
  5182. elements.push( ele );
  5183. }
  5184. }
  5185. return new $$.Collection( cy, elements );
  5186. },
  5187. add: function(toAdd){
  5188. var self = this;
  5189. var cy = this._private.cy;
  5190. if( !toAdd ){
  5191. return this;
  5192. }
  5193. if( $$.is.string(toAdd) ){
  5194. var selector = toAdd;
  5195. toAdd = cy.elements(selector);
  5196. }
  5197. var elements = [];
  5198. var ids = {};
  5199. function add(element){
  5200. if( !element ){
  5201. return;
  5202. }
  5203. if( !ids[ element.id() ] ){
  5204. elements.push( element );
  5205. ids[ element.id() ] = true;
  5206. }
  5207. }
  5208. // add own
  5209. for( var i = 0; i < self.length; i++ ){
  5210. var element = self[i];
  5211. add(element);
  5212. }
  5213. // add toAdd
  5214. for( var i = 0; i < toAdd.length; i++ ){
  5215. var element = toAdd[i];
  5216. add(element);
  5217. }
  5218. return new $$.Collection(cy, elements);
  5219. }
  5220. });
  5221. $$.fn.eles({
  5222. // do a breadth first search from the nodes in the collection
  5223. // from pseudocode on wikipedia
  5224. breadthFirstSearch: function( fn, directed ){
  5225. fn = fn || function(){};
  5226. var cy = this._private.cy;
  5227. var v = this;
  5228. var Q = [];
  5229. var marked = {};
  5230. var id2depth = {};
  5231. var connectedFrom = {};
  5232. var connectedEles = [];
  5233. // enqueue v
  5234. for( var i = 0; i < v.length; i++ ){
  5235. if( v[i].isNode() ){
  5236. Q.unshift( v[i] );
  5237. // and mark v
  5238. marked[ v[i].id() ] = true;
  5239. id2depth[ v[i].id() ] = 0;
  5240. connectedEles.push( v[i] );
  5241. }
  5242. }
  5243. i = 0;
  5244. while( Q.length !== 0 ){ // while Q not empty
  5245. var t = Q.shift();
  5246. var depth = 0;
  5247. var fromNodeId = connectedFrom[ t.id() ];
  5248. while( fromNodeId ){
  5249. depth++;
  5250. fromNodeId = connectedFrom[ fromNodeId ];
  5251. }
  5252. id2depth[ t.id() ] = depth;
  5253. var ret = fn.call(t, i, depth);
  5254. i++;
  5255. // on return true, return the result
  5256. if( ret === true ){
  5257. return new $$.Collection( cy, [ t ] );
  5258. }
  5259. // on return false, stop iteration
  5260. else if( ret === false ){
  5261. break;
  5262. }
  5263. var adjacentEdges = t.connectedEdges(directed ? '[source = "' + t.id() + '"]' : undefined);
  5264. for( var j = 0; j < adjacentEdges.length; j++ ){
  5265. var e = adjacentEdges[j];
  5266. var u = e.connectedNodes('[id != "' + t.id() + '"]');
  5267. if( u.length !== 0 ){
  5268. u = u[0];
  5269. if( !marked[ u.id() ] ){
  5270. marked[ u.id() ] = true; // mark u
  5271. Q.unshift( u ); // enqueue u onto Q
  5272. connectedFrom[ u.id() ] = t.id();
  5273. connectedEles.push( u );
  5274. connectedEles.push( e );
  5275. }
  5276. }
  5277. }
  5278. }
  5279. return new $$.Collection( cy, connectedEles ); // return none
  5280. },
  5281. // do a depth first search on the nodes in the collection
  5282. // from pseudocode on wikipedia (iterative impl)
  5283. depthFirstSearch: function( fn, directed ){
  5284. fn = fn || function(){};
  5285. var cy = this._private.cy;
  5286. var v = this;
  5287. var S = [];
  5288. var discovered = [];
  5289. var forwardEdge = {};
  5290. var backEdge = {};
  5291. var crossEdge = {};
  5292. var treeEdge = {};
  5293. var explored = {};
  5294. function labelled(e){
  5295. var id = e.id();
  5296. return forwardEdge[id] || backEdge[id] || crossEdge[id] || treeEdge[id];
  5297. }
  5298. // push v
  5299. for( var i = 0; i < v.length; i++ ){
  5300. if( v[i].isNode() ){
  5301. S.push( v[i] );
  5302. // and mark discovered
  5303. discovered[ v[i].id() ] = true;
  5304. }
  5305. }
  5306. while( S.length !== 0 ){
  5307. var t = S[ S.length - 1 ];
  5308. var ret = fn.call(t);
  5309. var breaked = false;
  5310. if( ret === true ){
  5311. return new $$.Collection( cy, [t] );
  5312. }
  5313. var adjacentEdges = t.connectedEdges(directed ? '[source = "' + t.id() + '"]' : undefined);
  5314. for( var i = 0; i < adjacentEdges.length; i++ ){
  5315. var e = adjacentEdges[i];
  5316. if( labelled(e) ){
  5317. continue;
  5318. }
  5319. var w = e.connectedNodes('[id != "' + t.id() + '"]');
  5320. if( w.length !== 0 ){
  5321. w = w[0];
  5322. var wid = w.id();
  5323. if( !discovered[wid] && !explored[wid] ){
  5324. treeEdge[wid] = true;
  5325. discovered[wid] = true;
  5326. S.push(w);
  5327. breaked = true;
  5328. break;
  5329. } else if( discovered[wid] ){
  5330. backEdge[wid] = true;
  5331. } else {
  5332. crossEdge[wid] = true;
  5333. }
  5334. }
  5335. }
  5336. if( !breaked ){
  5337. explored[ t.id() ] = true;
  5338. S.pop();
  5339. }
  5340. }
  5341. },
  5342. // get the root nodes in the DAG
  5343. roots: function( selector ){
  5344. var eles = this;
  5345. var roots = [];
  5346. for( var i = 0; i < eles.length; i++ ){
  5347. var ele = eles[i];
  5348. if( !ele.isNode() ){
  5349. continue;
  5350. }
  5351. var hasEdgesPointingIn = ele.connectedEdges('[target = "' + ele.id() + '"][source != "' + ele.id() + '"]').length > 0;
  5352. if( !hasEdgesPointingIn ){
  5353. roots.push( ele );
  5354. }
  5355. }
  5356. return new $$.Collection( this._private.cy, roots ).filter( selector );
  5357. },
  5358. // kruskal's algorithm (finds min spanning tree, assuming undirected graph)
  5359. // implemented from pseudocode from wikipedia
  5360. kruskal: function( weightFn ){
  5361. weightFn = weightFn || function(){ return 1; }; // if not specified, assume each edge has equal weight (1)
  5362. function findSet(ele){
  5363. for( var i = 0; i < forest.length; i++ ){
  5364. var eles = forest[i];
  5365. if( eles.anySame(ele) ){
  5366. return {
  5367. eles: eles,
  5368. index: i
  5369. };
  5370. }
  5371. }
  5372. }
  5373. var A = new $$.Collection(this._private.cy, []);
  5374. var forest = [];
  5375. var nodes = this.nodes();
  5376. for( var i = 0; i < nodes.length; i++ ){
  5377. forest.push( nodes[i].collection() );
  5378. }
  5379. var edges = this.edges();
  5380. var S = edges.toArray().sort(function(a, b){
  5381. var weightA = weightFn.call(a);
  5382. var weightB = weightFn.call(b);
  5383. return weightA - weightB;
  5384. });
  5385. for(var i = 0; i < S.length; i++){
  5386. var edge = S[i];
  5387. var u = edge.source()[0];
  5388. var v = edge.target()[0];
  5389. var setU = findSet(u);
  5390. var setV = findSet(v);
  5391. if( setU.eles !== setV.eles ){
  5392. A = A.add( edge );
  5393. forest[ setU.index ] = setU.eles.add( setV.eles );
  5394. forest.splice( setV.index, 1 );
  5395. }
  5396. }
  5397. return nodes.add( A );
  5398. },
  5399. dijkstra: function( target, weightFn, directed ){
  5400. var cy = this._private.cy;
  5401. directed = !$$.is.fn(weightFn) ? weightFn : directed;
  5402. directed = directed === undefined || directed;
  5403. weightFn = $$.is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)
  5404. if( this.length === 0 || !target || !$$.is.elementOrCollection(target) || target.length === 0 ){
  5405. return new $$.Collection(cy, []);
  5406. }
  5407. var source = this[0];
  5408. target = target[0];
  5409. var dist = {};
  5410. var prev = {};
  5411. var nodes = cy.nodes();
  5412. for( var i = 0; i < nodes.length; i++ ){
  5413. dist[ nodes[i].id() ] = Infinity;
  5414. }
  5415. dist[ source.id() ] = 0;
  5416. var Q = nodes;
  5417. var smallestDist = function(Q){
  5418. var smallest = Infinity;
  5419. var index;
  5420. for(var i in dist){
  5421. if( dist[i] < smallest && Q.$('#' + i).length !== 0 ){
  5422. smallest = dist[i];
  5423. index = i;
  5424. }
  5425. }
  5426. return index;
  5427. };
  5428. var distBetween = function(u, v){
  5429. var edges = u.edgesWith(v);
  5430. var smallestDistance = Infinity;
  5431. var smallestEdge;
  5432. for( var i = 0; i < edges.length; i++ ){
  5433. var edge = edges[i];
  5434. var weight = weightFn.call(edge);
  5435. if( weight < smallestDistance ){
  5436. smallestDistance = weight;
  5437. smallestEdge = edge;
  5438. }
  5439. }
  5440. return {
  5441. edge: smallestEdge,
  5442. dist: smallestDistance
  5443. };
  5444. };
  5445. while( Q.length !== 0 ){
  5446. var uid = smallestDist(Q);
  5447. var u = Q.filter('#' + uid);
  5448. if( u.length === 0 ){
  5449. continue;
  5450. }
  5451. //debugger;
  5452. Q = Q.not( u );
  5453. if( u.same(target) ){
  5454. break;
  5455. }
  5456. if( dist[uid] === Math.Infinite ){
  5457. break;
  5458. }
  5459. var neighbors = u.neighborhood().nodes();
  5460. for( var i = 0; i < neighbors.length; i++ ){
  5461. var v = neighbors[i];
  5462. var vid = v.id()
  5463. var duv = distBetween(u, v);
  5464. var alt = dist[uid] + duv.dist;
  5465. if( alt < dist[vid] ){
  5466. dist[vid] = alt;
  5467. prev[vid] = {
  5468. node: v,
  5469. edge: duv.edge
  5470. };
  5471. // TODO decrease-key v in Q
  5472. }
  5473. }
  5474. }
  5475. }
  5476. });
  5477. // nice, short mathemathical alias
  5478. $$.elesfn.bfs = $$.elesfn.breadthFirstSearch;
  5479. $$.elesfn.dfs = $$.elesfn.depthFirstSearch;
  5480. // Neighbourhood functions
  5481. //////////////////////////
  5482. $$.fn.eles({
  5483. neighborhood: function(selector){
  5484. var elements = [];
  5485. var cy = this._private.cy;
  5486. var nodes = this.nodes();
  5487. for( var i = 0; i < nodes.length; i++ ){ // for all nodes
  5488. var node = nodes[i];
  5489. var connectedEdges = node.connectedEdges();
  5490. // for each connected edge, add the edge and the other node
  5491. for( var j = 0; j < connectedEdges.length; j++ ){
  5492. var edge = connectedEdges[j];
  5493. var otherNode = edge.connectedNodes().not(node);
  5494. // need check in case of loop
  5495. if( otherNode.length > 0 ){
  5496. elements.push( otherNode[0] ); // add node 1 hop away
  5497. }
  5498. // add connected edge
  5499. elements.push( edge[0] );
  5500. }
  5501. }
  5502. return ( new $$.Collection( cy, elements ) ).filter( selector );
  5503. },
  5504. closedNeighborhood: function(selector){
  5505. return this.neighborhood().add(this).filter(selector);
  5506. },
  5507. openNeighborhood: function(selector){
  5508. return this.neighborhood(selector);
  5509. }
  5510. });
  5511. // Edge functions
  5512. /////////////////
  5513. $$.fn.eles({
  5514. source: defineSourceFunction({
  5515. attr: "source"
  5516. }),
  5517. target: defineSourceFunction({
  5518. attr: "target"
  5519. })
  5520. });
  5521. function defineSourceFunction( params ){
  5522. return function( selector ){
  5523. var sources = [];
  5524. var edges = this.edges();
  5525. var cy = this._private.cy;
  5526. for( var i = 0; i < edges.length; i++ ){
  5527. var edge = edges[i];
  5528. var id = edge._private.data[params.attr];
  5529. var src = cy.getElementById( id );
  5530. if( src.length > 0 ){
  5531. sources.push( src );
  5532. }
  5533. }
  5534. return new $$.Collection( cy, sources ).filter( selector );
  5535. }
  5536. }
  5537. $$.fn.eles({
  5538. edgesWith: defineEdgesWithFunction(),
  5539. edgesTo: defineEdgesWithFunction({
  5540. thisIs: "source"
  5541. })
  5542. });
  5543. function defineEdgesWithFunction( params ){
  5544. return function(otherNodes){
  5545. var elements = [];
  5546. var cy = this._private.cy;
  5547. var p = params || {};
  5548. // get elements if a selector is specified
  5549. if( $$.is.string(otherNodes) ){
  5550. otherNodes = cy.$( otherNodes );
  5551. }
  5552. var edges = otherNodes.connectedEdges();
  5553. var thisIds = this._private.ids;
  5554. for( var i = 0; i < edges.length; i++ ){
  5555. var edge = edges[i];
  5556. var foundId;
  5557. var edgeData = edge._private.data;
  5558. if( p.thisIs ){
  5559. var idToFind = edgeData[ p.thisIs ];
  5560. foundId = thisIds[ idToFind ];
  5561. } else {
  5562. foundId = thisIds[ edgeData.source ] || thisIds[ edgeData.target ];
  5563. }
  5564. if( foundId ){
  5565. elements.push( edge );
  5566. }
  5567. }
  5568. return new $$.Collection( cy, elements );
  5569. };
  5570. }
  5571. $$.fn.eles({
  5572. connectedEdges: function( selector ){
  5573. var elements = [];
  5574. var cy = this._private.cy;
  5575. var nodes = this.nodes();
  5576. for( var i = 0; i < nodes.length; i++ ){
  5577. var node = nodes[i];
  5578. var edges = node._private.edges;
  5579. for( var j = 0; j < edges.length; j++ ){
  5580. var edge = edges[j];
  5581. elements.push( edge );
  5582. }
  5583. }
  5584. return new $$.Collection( cy, elements ).filter( selector );
  5585. },
  5586. connectedNodes: function( selector ){
  5587. var elements = [];
  5588. var cy = this._private.cy;
  5589. var edges = this.edges();
  5590. for( var i = 0; i < edges.length; i++ ){
  5591. var edge = edges[i];
  5592. elements.push( edge.source()[0] );
  5593. elements.push( edge.target()[0] );
  5594. }
  5595. return new $$.Collection( cy, elements ).filter( selector );
  5596. },
  5597. parallelEdges: defineParallelEdgesFunction(),
  5598. codirectedEdges: defineParallelEdgesFunction({
  5599. codirected: true
  5600. }),
  5601. parallelIndex: function(){
  5602. var edge = this[0];
  5603. if( edge.isEdge() ){
  5604. var src = edge.source()[0];
  5605. var srcEdges = src._private.edges;
  5606. var index = 0;
  5607. for( var i = 0; i < srcEdges.length; i++ ){
  5608. var srcEdge = srcEdges[i];
  5609. var thisIsTheIndex = srcEdge === edge;
  5610. if( thisIsTheIndex ){
  5611. return index;
  5612. }
  5613. var codirected = edge._private.data.source === srcEdge._private.data.source
  5614. && edge._private.data.target === srcEdge._private.data.target;
  5615. var opdirected = edge._private.data.source === srcEdge._private.data.target
  5616. && edge._private.data.target === srcEdge._private.data.source;
  5617. var parallel = codirected || opdirected;
  5618. if( parallel ){ // then increase the count
  5619. index++;
  5620. }
  5621. }
  5622. }
  5623. },
  5624. parallelSize: function(){
  5625. var edge = this[0];
  5626. if( edge.isEdge() ){
  5627. var src = edge.source()[0];
  5628. var srcEdges = src._private.edges;
  5629. var numEdges = 0;
  5630. for( var i = 0; i < srcEdges.length; i++ ){
  5631. var srcEdge = srcEdges[i];
  5632. var codirected = edge._private.data.source === srcEdge._private.data.source
  5633. && edge._private.data.target === srcEdge._private.data.target;
  5634. var opdirected = edge._private.data.source === srcEdge._private.data.target
  5635. && edge._private.data.target === srcEdge._private.data.source;
  5636. var parallel = codirected || opdirected;
  5637. if( parallel ){ // then increase the count
  5638. numEdges++;
  5639. }
  5640. }
  5641. return numEdges;
  5642. }
  5643. }
  5644. });
  5645. function defineParallelEdgesFunction(params){
  5646. var defaults = {
  5647. codirected: false
  5648. };
  5649. params = $$.util.extend({}, defaults, params);
  5650. return function( selector ){
  5651. var cy = this._private.cy;
  5652. var elements = [];
  5653. var edges = this.edges();
  5654. var p = params;
  5655. // look at all the edges in the collection
  5656. for( var i = 0; i < edges.length; i++ ){
  5657. var edge1 = edges[i];
  5658. var src1 = edge1.source()[0];
  5659. var srcid1 = src1.id();
  5660. var tgt1 = edge1.target()[0];
  5661. var tgtid1 = tgt1.id();
  5662. var srcEdges1 = src1._private.edges;
  5663. // look at edges connected to the src node of this edge
  5664. for( var j = 0; j < srcEdges1.length; j++ ){
  5665. var edge2 = srcEdges1[j];
  5666. var edge2data = edge2._private.data;
  5667. var tgtid2 = edge2data.target;
  5668. var srcid2 = edge2data.source;
  5669. var codirected = tgtid2 === tgtid1 && srcid2 === srcid1;
  5670. var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2;
  5671. if( (p.codirected && codirected)
  5672. || (!p.codirected && (codirected || oppdirected)) ){
  5673. elements.push( edge2 );
  5674. }
  5675. }
  5676. }
  5677. return new $$.Collection( cy, elements ).filter( selector );
  5678. };
  5679. }
  5680. // Compound functions
  5681. /////////////////////
  5682. $$.fn.eles({
  5683. parent: function( selector ){
  5684. var parents = [];
  5685. var cy = this._private.cy;
  5686. for( var i = 0; i < this.length; i++ ){
  5687. var ele = this[i];
  5688. var parent = cy.getElementById( ele._private.data.parent );
  5689. if( parent.size() > 0 ){
  5690. parents.push( parent );
  5691. }
  5692. }
  5693. return new $$.Collection( cy, parents ).filter( selector );
  5694. },
  5695. parents: function( selector ){
  5696. var parents = [];
  5697. var eles = this.parent();
  5698. while( eles.nonempty() ){
  5699. for( var i = 0; i < eles.length; i++ ){
  5700. var ele = eles[i];
  5701. parents.push( ele );
  5702. }
  5703. eles = eles.parent();
  5704. }
  5705. return new $$.Collection( this.cy(), parents ).filter( selector );
  5706. },
  5707. children: function( selector ){
  5708. var children = [];
  5709. for( var i = 0; i < this.length; i++ ){
  5710. var ele = this[i];
  5711. children = children.concat( ele._private.children );
  5712. }
  5713. return new $$.Collection( this.cy(), children ).filter( selector );
  5714. },
  5715. siblings: function( selector ){
  5716. return this.parent().children().not( this ).filter( selector );
  5717. },
  5718. isParent: function(){
  5719. var ele = this[0];
  5720. if( ele ){
  5721. return ele._private.children.length !== 0;
  5722. }
  5723. },
  5724. isChild: function(){
  5725. var ele = this[0];
  5726. if( ele ){
  5727. return ele._private.data.parent !== undefined && ele.parent().length !== 0;
  5728. }
  5729. },
  5730. descendants: function( selector ){
  5731. var elements = [];
  5732. function add( eles ){
  5733. for( var i = 0; i < eles.length; i++ ){
  5734. var ele = eles[i];
  5735. elements.push( ele );
  5736. if( ele.children().nonempty() ){
  5737. add( ele.children() );
  5738. }
  5739. }
  5740. }
  5741. add( this.children() );
  5742. return new $$.Collection( this.cy(), elements ).filter( selector );
  5743. }
  5744. });
  5745. })( cytoscape );
  5746. ;(function($$){
  5747. $$.fn.selector = function(map, options){
  5748. for( var name in map ){
  5749. var fn = map[name];
  5750. $$.Selector.prototype[ name ] = fn;
  5751. }
  5752. };
  5753. $$.Selector = function(onlyThisGroup, selector){
  5754. if( !(this instanceof $$.Selector) ){
  5755. return new $$.Selector(onlyThisGroup, selector);
  5756. }
  5757. if( selector === undefined && onlyThisGroup !== undefined ){
  5758. selector = onlyThisGroup;
  5759. onlyThisGroup = undefined;
  5760. }
  5761. var self = this;
  5762. self._private = {
  5763. selectorText: null,
  5764. invalid: true
  5765. }
  5766. // storage for parsed queries
  5767. // when you add something here, also add to Selector.toString()
  5768. function newQuery(){
  5769. return {
  5770. classes: [],
  5771. colonSelectors: [],
  5772. data: [],
  5773. group: null,
  5774. ids: [],
  5775. meta: [],
  5776. // fake selectors
  5777. collection: null, // a collection to match against
  5778. filter: null, // filter function
  5779. // these are defined in the upward direction rather than down (e.g. child)
  5780. // because we need to go up in Selector.filter()
  5781. parent: null, // parent query obj
  5782. ancestor: null, // ancestor query obj
  5783. subject: null, // defines subject in compound query (subject query obj; points to self if subject)
  5784. // use these only when subject has been defined
  5785. child: null,
  5786. descendant: null
  5787. };
  5788. }
  5789. if( !selector || ( $$.is.string(selector) && selector.match(/^\s*$/) ) ){
  5790. if( onlyThisGroup == null ){
  5791. // ignore
  5792. self.length = 0;
  5793. } else {
  5794. self[0] = newQuery();
  5795. self[0].group = onlyThisGroup;
  5796. self.length = 1;
  5797. }
  5798. } else if( $$.is.element( selector ) ){
  5799. var collection = new $$.Collection(self.cy(), [ selector ]);
  5800. self[0] = newQuery();
  5801. self[0].collection = collection;
  5802. self.length = 1;
  5803. } else if( $$.is.collection( selector ) ){
  5804. self[0] = newQuery();
  5805. self[0].collection = selector;
  5806. self.length = 1;
  5807. } else if( $$.is.fn( selector ) ) {
  5808. self[0] = newQuery();
  5809. self[0].filter = selector;
  5810. self.length = 1;
  5811. } else if( $$.is.string( selector ) ){
  5812. // these are the actual tokens in the query language
  5813. var metaChar = "[\\!\\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]"; // chars we need to escape in var names, etc
  5814. var variable = "(?:[\\w-]|(?:\\\\"+ metaChar +"))+"; // a variable name
  5815. var comparatorOp = "=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*="; // binary comparison op (used in data selectors)
  5816. var boolOp = "\\?|\\!|\\^"; // boolean (unary) operators (used in data selectors)
  5817. var string = '"(?:\\\\"|[^"])+"' + "|" + "'(?:\\\\'|[^'])+'"; // string literals (used in data selectors) -- doublequotes | singlequotes
  5818. var number = $$.util.regex.number; // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123
  5819. var value = string + "|" + number; // a value literal, either a string or number
  5820. var meta = "degree|indegree|outdegree"; // allowed metadata fields (i.e. allowed functions to use from $$.Collection)
  5821. var separator = "\\s*,\\s*"; // queries are separated by commas; e.g. edge[foo = "bar"], node.someClass
  5822. var className = variable; // a class name (follows variable conventions)
  5823. var descendant = "\\s+";
  5824. var child = "\\s+>\\s+";
  5825. var subject = "\\$";
  5826. var id = variable; // an element id (follows variable conventions)
  5827. // when a token like a variable has escaped meta characters, we need to clean the backslashes out
  5828. // so that values get compared properly in Selector.filter()
  5829. function cleanMetaChars(str){
  5830. return str.replace(new RegExp("\\\\(" + metaChar + ")", "g"), "\1");
  5831. }
  5832. // add @ variants to comparatorOp
  5833. var ops = comparatorOp.split("|");
  5834. for( var i = 0; i < ops.length; i++ ){
  5835. var op = ops[i];
  5836. comparatorOp += "|@" + op;
  5837. }
  5838. // the current subject in the query
  5839. var currentSubject = null;
  5840. // NOTE: add new expression syntax here to have it recognised by the parser;
  5841. // a query contains all adjacent (i.e. no separator in between) expressions;
  5842. // the current query is stored in self[i] --- you can use the reference to `this` in the populate function;
  5843. // you need to check the query objects in Selector.filter() for it actually filter properly, but that's pretty straight forward
  5844. var exprs = {
  5845. group: {
  5846. query: true,
  5847. regex: "(node|edge|\\*)",
  5848. populate: function( group ){
  5849. this.group = group == "*" ? group : group + "s";
  5850. }
  5851. },
  5852. state: {
  5853. query: true,
  5854. regex: "(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:parent|:child|:active|:inactive|:touch)",
  5855. populate: function( state ){
  5856. this.colonSelectors.push( state );
  5857. }
  5858. },
  5859. id: {
  5860. query: true,
  5861. regex: "\\#("+ id +")",
  5862. populate: function( id ){
  5863. this.ids.push( cleanMetaChars(id) );
  5864. }
  5865. },
  5866. className: {
  5867. query: true,
  5868. regex: "\\.("+ className +")",
  5869. populate: function( className ){
  5870. this.classes.push( cleanMetaChars(className) );
  5871. }
  5872. },
  5873. dataExists: {
  5874. query: true,
  5875. regex: "\\[\\s*("+ variable +")\\s*\\]",
  5876. populate: function( variable ){
  5877. this.data.push({
  5878. field: cleanMetaChars(variable)
  5879. });
  5880. }
  5881. },
  5882. dataCompare: {
  5883. query: true,
  5884. regex: "\\[\\s*("+ variable +")\\s*("+ comparatorOp +")\\s*("+ value +")\\s*\\]",
  5885. populate: function( variable, comparatorOp, value ){
  5886. this.data.push({
  5887. field: cleanMetaChars(variable),
  5888. operator: comparatorOp,
  5889. value: value
  5890. });
  5891. }
  5892. },
  5893. dataBool: {
  5894. query: true,
  5895. regex: "\\[\\s*("+ boolOp +")\\s*("+ variable +")\\s*\\]",
  5896. populate: function( boolOp, variable ){
  5897. this.data.push({
  5898. field: cleanMetaChars(variable),
  5899. operator: boolOp
  5900. });
  5901. }
  5902. },
  5903. metaCompare: {
  5904. query: true,
  5905. regex: "\\{\\s*("+ meta +")\\s*("+ comparatorOp +")\\s*("+ number +")\\s*\\}",
  5906. populate: function( meta, comparatorOp, number ){
  5907. this.meta.push({
  5908. field: cleanMetaChars(meta),
  5909. operator: comparatorOp,
  5910. value: number
  5911. });
  5912. }
  5913. },
  5914. nextQuery: {
  5915. separator: true,
  5916. regex: separator,
  5917. populate: function(){
  5918. // go on to next query
  5919. self[++i] = newQuery();
  5920. currentSubject = null;
  5921. }
  5922. },
  5923. child: {
  5924. separator: true,
  5925. regex: child,
  5926. populate: function(){
  5927. // this query is the parent of the following query
  5928. var childQuery = newQuery();
  5929. childQuery.parent = this;
  5930. childQuery.subject = currentSubject;
  5931. // we're now populating the child query with expressions that follow
  5932. self[i] = childQuery;
  5933. }
  5934. },
  5935. descendant: {
  5936. separator: true,
  5937. regex: descendant,
  5938. populate: function(){
  5939. // this query is the ancestor of the following query
  5940. var descendantQuery = newQuery();
  5941. descendantQuery.ancestor = this;
  5942. descendantQuery.subject = currentSubject;
  5943. // we're now populating the descendant query with expressions that follow
  5944. self[i] = descendantQuery;
  5945. }
  5946. },
  5947. subject: {
  5948. modifier: true,
  5949. regex: subject,
  5950. populate: function(){
  5951. if( currentSubject != null && this.subject != this ){
  5952. $$.util.error("Redefinition of subject in selector `" + selector + "`");
  5953. return false;
  5954. }
  5955. currentSubject = this;
  5956. this.subject = this;
  5957. },
  5958. }
  5959. };
  5960. var j = 0;
  5961. for( var name in exprs ){
  5962. exprs[j] = exprs[name];
  5963. exprs[j].name = name;
  5964. j++;
  5965. }
  5966. exprs.length = j;
  5967. self._private.selectorText = selector;
  5968. var remaining = selector;
  5969. var i = 0;
  5970. // of all the expressions, find the first match in the remaining text
  5971. function consumeExpr( expectation ){
  5972. var expr;
  5973. var match;
  5974. var name;
  5975. for( var j = 0; j < exprs.length; j++ ){
  5976. var e = exprs[j];
  5977. var n = e.name;
  5978. // ignore this expression if it doesn't meet the expectation function
  5979. if( $$.is.fn( expectation ) && !expectation(n, e) ){ continue }
  5980. var m = remaining.match(new RegExp( "^" + e.regex ));
  5981. if( m != null ){
  5982. match = m;
  5983. expr = e;
  5984. name = n;
  5985. var consumed = m[0];
  5986. remaining = remaining.substring( consumed.length );
  5987. break; // we've consumed one expr, so we can return now
  5988. }
  5989. }
  5990. return {
  5991. expr: expr,
  5992. match: match,
  5993. name: name
  5994. };
  5995. }
  5996. // consume all leading whitespace
  5997. function consumeWhitespace(){
  5998. var match = remaining.match(/^\s+/);
  5999. if( match ){
  6000. var consumed = match[0];
  6001. remaining = remaining.substring( consumed.length );
  6002. }
  6003. }
  6004. self[0] = newQuery(); // get started
  6005. consumeWhitespace(); // get rid of leading whitespace
  6006. for(;;){
  6007. var check = consumeExpr();
  6008. if( check.expr == null ){
  6009. $$.util.error("The selector `"+ selector +"`is invalid");
  6010. return;
  6011. } else {
  6012. var args = [];
  6013. for(var j = 1; j < check.match.length; j++){
  6014. args.push( check.match[j] );
  6015. }
  6016. // let the token populate the selector object (i.e. in self[i])
  6017. var ret = check.expr.populate.apply( self[i], args );
  6018. if( ret === false ){ return } // exit if population failed
  6019. }
  6020. // we're done when there's nothing left to parse
  6021. if( remaining.match(/^\s*$/) ){
  6022. break;
  6023. }
  6024. }
  6025. self.length = i + 1;
  6026. // adjust references for subject
  6027. for(j = 0; j < self.length; j++){
  6028. var query = self[j];
  6029. if( query.subject != null ){
  6030. // go up the tree until we reach the subject
  6031. for(;;){
  6032. if( query.subject == query ){ break } // done if subject is self
  6033. if( query.parent != null ){ // swap parent/child reference
  6034. var parent = query.parent;
  6035. var child = query;
  6036. child.parent = null;
  6037. parent.child = child;
  6038. query = parent; // go up the tree
  6039. } else if( query.ancestor != null ){ // swap ancestor/descendant
  6040. var ancestor = query.ancestor;
  6041. var descendant = query;
  6042. descendant.ancestor = null;
  6043. ancestor.descendant = descendant;
  6044. query = ancestor; // go up the tree
  6045. } else {
  6046. $$.util.error("When adjusting references for the selector `"+ query +"`, neither parent nor ancestor was found");
  6047. break;
  6048. }
  6049. } // for
  6050. self[j] = query.subject; // subject should be the root query
  6051. } // if
  6052. } // for
  6053. // make sure for each query that the subject group matches the implicit group if any
  6054. if( onlyThisGroup != null ){
  6055. for(var j = 0; j < self.length; j++){
  6056. if( self[j].group != null && self[j].group != onlyThisGroup ){
  6057. $$.util.error("Group `"+ self[j].group +"` conflicts with implicit group `"+ onlyThisGroup +"` in selector `"+ selector +"`");
  6058. return;
  6059. }
  6060. self[j].group = onlyThisGroup; // set to implicit group
  6061. }
  6062. }
  6063. } else {
  6064. $$.util.error("A selector must be created from a string; found " + selector);
  6065. return;
  6066. }
  6067. self._private.invalid = false;
  6068. };
  6069. $$.selfn = $$.Selector.prototype;
  6070. $$.selfn.size = function(){
  6071. return this.length;
  6072. };
  6073. $$.selfn.eq = function(i){
  6074. return this[i];
  6075. };
  6076. // get elements from the core and then filter them
  6077. $$.selfn.find = function(){
  6078. // TODO impl if we decide to use a DB for storing elements
  6079. };
  6080. // filter an existing collection
  6081. $$.selfn.filter = function(collection, addLiveFunction){
  6082. var self = this;
  6083. var cy = collection.cy();
  6084. // don't bother trying if it's invalid
  6085. if( self._private.invalid ){
  6086. return new $$.Collection( cy );
  6087. }
  6088. var queryMatches = function(query, element){
  6089. // check group
  6090. if( query.group != null && query.group != "*" && query.group != element._private.group ){
  6091. return false;
  6092. }
  6093. // check colon selectors
  6094. var allColonSelectorsMatch = true;
  6095. for(var k = 0; k < query.colonSelectors.length; k++){
  6096. var sel = query.colonSelectors[k];
  6097. var renderer = cy.renderer(); // TODO remove reference after refactoring
  6098. switch(sel){
  6099. case ":selected":
  6100. allColonSelectorsMatch = element.selected();
  6101. break;
  6102. case ":unselected":
  6103. allColonSelectorsMatch = !element.selected();
  6104. break;
  6105. case ":selectable":
  6106. allColonSelectorsMatch = element.selectable();
  6107. break;
  6108. case ":unselectable":
  6109. allColonSelectorsMatch = !element.selectable();
  6110. break;
  6111. case ":locked":
  6112. allColonSelectorsMatch = element.locked();
  6113. break;
  6114. case ":unlocked":
  6115. allColonSelectorsMatch = !element.locked();
  6116. break;
  6117. case ":visible":
  6118. allColonSelectorsMatch = element.visible();
  6119. break;
  6120. case ":hidden":
  6121. allColonSelectorsMatch = !element.visible();
  6122. break;
  6123. case ":grabbed":
  6124. allColonSelectorsMatch = element.grabbed();
  6125. break;
  6126. case ":free":
  6127. allColonSelectorsMatch = !element.grabbed();
  6128. break;
  6129. case ":removed":
  6130. allColonSelectorsMatch = element.removed();
  6131. break;
  6132. case ":inside":
  6133. allColonSelectorsMatch = !element.removed();
  6134. break;
  6135. case ":grabbable":
  6136. allColonSelectorsMatch = element.grabbable();
  6137. break;
  6138. case ":ungrabbable":
  6139. allColonSelectorsMatch = !element.grabbable();
  6140. break;
  6141. case ":animated":
  6142. allColonSelectorsMatch = element.animated();
  6143. break;
  6144. case ":unanimated":
  6145. allColonSelectorsMatch = !element.animated();
  6146. break;
  6147. case ":parent":
  6148. allColonSelectorsMatch = element.children().nonempty();
  6149. break;
  6150. case ":child":
  6151. allColonSelectorsMatch = element.parent().nonempty();
  6152. break;
  6153. case ":active":
  6154. allColonSelectorsMatch = element.active();
  6155. break;
  6156. case ":inactive":
  6157. allColonSelectorsMatch = !element.active();
  6158. break;
  6159. case ":touch":
  6160. allColonSelectorsMatch = window && document && (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
  6161. break;
  6162. }
  6163. if( !allColonSelectorsMatch ) break;
  6164. }
  6165. if( !allColonSelectorsMatch ) return false;
  6166. // check id
  6167. var allIdsMatch = true;
  6168. for(var k = 0; k < query.ids.length; k++){
  6169. var id = query.ids[k];
  6170. var actualId = element._private.data.id;
  6171. allIdsMatch = allIdsMatch && (id == actualId);
  6172. if( !allIdsMatch ) break;
  6173. }
  6174. if( !allIdsMatch ) return false;
  6175. // check classes
  6176. var allClassesMatch = true;
  6177. for(var k = 0; k < query.classes.length; k++){
  6178. var cls = query.classes[k];
  6179. allClassesMatch = allClassesMatch && element.hasClass(cls);
  6180. if( !allClassesMatch ) break;
  6181. }
  6182. if( !allClassesMatch ) return false;
  6183. // generic checking for data/metadata
  6184. function operandsMatch(params){
  6185. var allDataMatches = true;
  6186. for(var k = 0; k < query[params.name].length; k++){
  6187. var data = query[params.name][k];
  6188. var operator = data.operator;
  6189. var value = data.value;
  6190. var field = data.field;
  6191. var matches;
  6192. if( operator != null && value != null ){
  6193. var fieldStr = "" + params.fieldValue(field);
  6194. var valStr = "" + eval(value);
  6195. var caseInsensitive = false;
  6196. if( operator.charAt(0) == "@" ){
  6197. fieldStr = fieldStr.toLowerCase();
  6198. valStr = valStr.toLowerCase();
  6199. operator = operator.substring(1);
  6200. caseInsensitive = true;
  6201. }
  6202. if( operator == "=" ){
  6203. operator = "==";
  6204. }
  6205. switch(operator){
  6206. case "*=":
  6207. matches = fieldStr.search(valStr) >= 0;
  6208. break;
  6209. case "$=":
  6210. matches = new RegExp(valStr + "$").exec(fieldStr) != null;
  6211. break;
  6212. case "^=":
  6213. matches = new RegExp("^" + valStr).exec(fieldStr) != null;
  6214. break;
  6215. default:
  6216. // if we're doing a case insensitive comparison, then we're using a STRING comparison
  6217. // even if we're comparing numbers
  6218. if( caseInsensitive ){
  6219. // eval with lower case strings
  6220. var expr = "fieldStr " + operator + " valStr";
  6221. matches = eval(expr);
  6222. } else {
  6223. // just eval as normal
  6224. var expr = params.fieldRef(field) + " " + operator + " " + value;
  6225. matches = eval(expr);
  6226. }
  6227. }
  6228. } else if( operator != null ){
  6229. switch(operator){
  6230. case "?":
  6231. matches = params.fieldTruthy(field);
  6232. break;
  6233. case "!":
  6234. matches = !params.fieldTruthy(field);
  6235. break;
  6236. case "^":
  6237. matches = params.fieldUndefined(field);
  6238. break;
  6239. }
  6240. } else {
  6241. matches = !params.fieldUndefined(field);
  6242. }
  6243. if( !matches ){
  6244. allDataMatches = false;
  6245. break;
  6246. }
  6247. } // for
  6248. return allDataMatches;
  6249. } // operandsMatch
  6250. // check data matches
  6251. var allDataMatches = operandsMatch({
  6252. name: "data",
  6253. fieldValue: function(field){
  6254. return element._private.data[field];
  6255. },
  6256. fieldRef: function(field){
  6257. return "element._private.data." + field;
  6258. },
  6259. fieldUndefined: function(field){
  6260. return element._private.data[field] === undefined;
  6261. },
  6262. fieldTruthy: function(field){
  6263. if( element._private.data[field] ){
  6264. return true;
  6265. }
  6266. return false;
  6267. }
  6268. });
  6269. if( !allDataMatches ){
  6270. return false;
  6271. }
  6272. // check metadata matches
  6273. var allMetaMatches = operandsMatch({
  6274. name: "meta",
  6275. fieldValue: function(field){
  6276. return element[field]();
  6277. },
  6278. fieldRef: function(field){
  6279. return "element." + field + "()";
  6280. },
  6281. fieldUndefined: function(field){
  6282. return element[field]() == undefined;
  6283. },
  6284. fieldTruthy: function(field){
  6285. if( element[field]() ){
  6286. return true;
  6287. }
  6288. return false;
  6289. }
  6290. });
  6291. if( !allMetaMatches ){
  6292. return false;
  6293. }
  6294. // check collection
  6295. if( query.collection != null ){
  6296. var matchesAny = query.collection._private.ids[ element.id() ] != null;
  6297. if( !matchesAny ){
  6298. return false;
  6299. }
  6300. }
  6301. // check filter function
  6302. if( query.filter != null && element.collection().filter( query.filter ).size() == 0 ){
  6303. return false;
  6304. }
  6305. // check parent/child relations
  6306. function confirmRelations( query, elements ){
  6307. if( query != null ){
  6308. var matches = false;
  6309. elements = elements(); // make elements functional so we save cycles if query == null
  6310. // query must match for at least one element (may be recursive)
  6311. for(var i = 0; i < elements.size(); i++){
  6312. if( queryMatches( query, elements.eq(i) ) ){
  6313. matches = true;
  6314. break;
  6315. }
  6316. }
  6317. return matches;
  6318. } else {
  6319. return true;
  6320. }
  6321. }
  6322. if (! confirmRelations(query.parent, function(){
  6323. return element.parent()
  6324. }) ){ return false }
  6325. if (! confirmRelations(query.ancestor, function(){
  6326. return element.parents()
  6327. }) ){ return false }
  6328. if (! confirmRelations(query.child, function(){
  6329. return element.children()
  6330. }) ){ return false }
  6331. if (! confirmRelations(query.descendant, function(){
  6332. return element.descendants()
  6333. }) ){ return false }
  6334. // we've reached the end, so we've matched everything for this query
  6335. return true;
  6336. }; // queryMatches
  6337. var selectorFunction = function(i, element){
  6338. for(var j = 0; j < self.length; j++){
  6339. var query = self[j];
  6340. if( queryMatches(query, element) ){
  6341. return true;
  6342. }
  6343. }
  6344. return false;
  6345. };
  6346. if( self._private.selectorText == null ){
  6347. selectorFunction = function(){ return true; };
  6348. }
  6349. var filteredCollection = collection.filter( selectorFunction );
  6350. return filteredCollection;
  6351. }; // filter
  6352. // ith query to string
  6353. $$.selfn.toString = $$.selfn.selector = function(){
  6354. var str = "";
  6355. function clean(obj){
  6356. if( $$.is.string(obj) ){
  6357. return obj;
  6358. }
  6359. return "";
  6360. }
  6361. function queryToString(query){
  6362. var str = "";
  6363. var group = clean(query.group);
  6364. str += group.substring(0, group.length - 1);
  6365. for(var j = 0; j < query.data.length; j++){
  6366. var data = query.data[j];
  6367. str += "[" + data.field + clean(data.operator) + clean(data.value) + "]"
  6368. }
  6369. for(var j = 0; j < query.meta.length; j++){
  6370. var meta = query.meta[j];
  6371. str += "{" + meta.field + clean(meta.operator) + clean(meta.value) + "}"
  6372. }
  6373. for(var j = 0; j < query.colonSelectors.length; j++){
  6374. var sel = query.colonSelectors[i];
  6375. str += sel;
  6376. }
  6377. for(var j = 0; j < query.ids.length; j++){
  6378. var sel = "#" + query.ids[i];
  6379. str += sel;
  6380. }
  6381. for(var j = 0; j < query.classes.length; j++){
  6382. var sel = "." + query.classes[i];
  6383. str += sel;
  6384. }
  6385. if( query.parent != null ){
  6386. str = queryToString( query.parent ) + " > " + str;
  6387. }
  6388. if( query.ancestor != null ){
  6389. str = queryToString( query.ancestor ) + " " + str;
  6390. }
  6391. if( query.child != null ){
  6392. str += " > " + queryToString( query.child );
  6393. }
  6394. if( query.descendant != null ){
  6395. str += " " + queryToString( query.descendant );
  6396. }
  6397. return str;
  6398. }
  6399. for(var i = 0; i < this.length; i++){
  6400. var query = this[i];
  6401. str += queryToString( query );
  6402. if( this.length > 1 && i < this.length - 1 ){
  6403. str += ", ";
  6404. }
  6405. }
  6406. return str;
  6407. };
  6408. })( cytoscape );
  6409. ;(function($$){
  6410. function NullRenderer(options){
  6411. }
  6412. NullRenderer.prototype.notify = function(params){
  6413. };
  6414. $$("renderer", "null", NullRenderer);
  6415. })( cytoscape );
  6416. (function($$) {
  6417. var isTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
  6418. var time = function() { return Date.now(); } ;
  6419. var arrowShapes = {}; var nodeShapes = {};
  6420. var rendFunc = CanvasRenderer.prototype;
  6421. var panOrBoxSelectDelay = 400;
  6422. // Canvas layer constants
  6423. var CANVAS_LAYERS = 5, SELECT_BOX = 0, DRAG = 2, OVERLAY = 3, NODE = 4, BUFFER_COUNT = 2;
  6424. function CanvasRenderer(options) {
  6425. this.options = options;
  6426. this.data = {
  6427. select: [undefined, undefined, undefined, undefined, 0], // Coordinates for selection box, plus enabled flag
  6428. renderer: this, cy: options.cy, container: options.cy.container(),
  6429. canvases: new Array(CANVAS_LAYERS),
  6430. canvasRedrawReason: new Array(CANVAS_LAYERS),
  6431. canvasNeedsRedraw: new Array(CANVAS_LAYERS),
  6432. bufferCanvases: new Array(BUFFER_COUNT)
  6433. };
  6434. //--Pointer-related data
  6435. this.hoverData = {down: null, last: null,
  6436. downTime: null, triggerMode: null,
  6437. dragging: false,
  6438. initialPan: [null, null], capture: false};
  6439. this.timeoutData = {panTimeout: null};
  6440. this.dragData = {possibleDragElements: []};
  6441. this.touchData = {start: null, capture: false,
  6442. // These 3 fields related to tap, taphold events
  6443. startPosition: [null, null, null, null, null, null],
  6444. singleTouchStartTime: null,
  6445. singleTouchMoved: true,
  6446. now: [null, null, null, null, null, null],
  6447. earlier: [null, null, null, null, null, null] };
  6448. //--
  6449. //--Wheel-related data
  6450. this.zoomData = {freeToZoom: false, lastPointerX: null};
  6451. //--
  6452. this.redraws = 0;
  6453. this.bindings = [];
  6454. this.init();
  6455. for (var i = 0; i < CANVAS_LAYERS; i++) {
  6456. this.data.canvases[i] = document.createElement("canvas");
  6457. this.data.canvases[i].style.position = "absolute";
  6458. this.data.canvases[i].setAttribute("data-id", "layer" + i);
  6459. this.data.canvases[i].style.zIndex = String(CANVAS_LAYERS - i);
  6460. this.data.container.appendChild(this.data.canvases[i]);
  6461. this.data.canvasRedrawReason[i] = new Array();
  6462. this.data.canvasNeedsRedraw[i] = false;
  6463. }
  6464. this.data.canvases[NODE].setAttribute("data-id", "layer" + NODE + '-node');
  6465. this.data.canvases[SELECT_BOX].setAttribute("data-id", "layer" + SELECT_BOX + '-selectbox');
  6466. this.data.canvases[DRAG].setAttribute("data-id", "layer" + DRAG + '-drag');
  6467. this.data.canvases[OVERLAY].setAttribute("data-id", "layer" + OVERLAY + '-overlay');
  6468. for (var i = 0; i < BUFFER_COUNT; i++) {
  6469. this.data.bufferCanvases[i] = document.createElement("canvas");
  6470. this.data.bufferCanvases[i].style.position = "absolute";
  6471. this.data.bufferCanvases[i].setAttribute("data-id", "buffer" + i);
  6472. this.data.bufferCanvases[i].style.zIndex = String(-i - 1);
  6473. this.data.bufferCanvases[i].style.visibility = "visible";
  6474. this.data.container.appendChild(this.data.bufferCanvases[i]);
  6475. }
  6476. var overlay = document.createElement('div');
  6477. this.data.container.appendChild( overlay );
  6478. this.data.overlay = overlay;
  6479. overlay.style.position = 'absolute';
  6480. overlay.style.zIndex = 1000;
  6481. if( options.showOverlay ){
  6482. var link = document.createElement('a');
  6483. overlay.appendChild( link );
  6484. this.data.link = link;
  6485. link.innerHTML = 'cytoscape.js';
  6486. link.style.font = '14px helvetica';
  6487. link.style.position = 'absolute';
  6488. link.style.right = 0;
  6489. link.style.bottom = 0;
  6490. link.style.padding = '1px 3px';
  6491. link.style.paddingLeft = '5px';
  6492. link.style.paddingTop = '5px';
  6493. link.style.opacity = 0;
  6494. link.style['-webkit-tap-highlight-color'] = 'transparent';
  6495. link.style.background = 'red';
  6496. link.href = 'http://cytoscape.github.io/cytoscape.js/';
  6497. link.target = '_blank';
  6498. }
  6499. this.hideEdgesOnViewport = options.hideEdgesOnViewport;
  6500. this.load();
  6501. }
  6502. CanvasRenderer.prototype.notify = function(params) {
  6503. if ( params.type == "destroy" ){
  6504. this.destroy();
  6505. return;
  6506. } else if (params.type == "add"
  6507. || params.type == "remove"
  6508. || params.type == "load"
  6509. ) {
  6510. this.updateNodesCache();
  6511. this.updateEdgesCache();
  6512. }
  6513. if (params.type == "viewport") {
  6514. this.data.canvasNeedsRedraw[SELECT_BOX] = true;
  6515. this.data.canvasRedrawReason[SELECT_BOX].push("viewchange");
  6516. }
  6517. this.data.canvasNeedsRedraw[DRAG] = true; this.data.canvasRedrawReason[DRAG].push("notify");
  6518. this.data.canvasNeedsRedraw[NODE] = true; this.data.canvasRedrawReason[NODE].push("notify");
  6519. this.redraws++;
  6520. this.redraw();
  6521. };
  6522. CanvasRenderer.prototype.registerBinding = function(target, event, handler, useCapture){
  6523. this.bindings.push({
  6524. target: target,
  6525. event: event,
  6526. handler: handler,
  6527. useCapture: useCapture
  6528. });
  6529. target.addEventListener(event, handler, useCapture);
  6530. };
  6531. CanvasRenderer.prototype.destroy = function(){
  6532. this.destroyed = true;
  6533. for( var i = 0; i < this.bindings.length; i++ ){
  6534. var binding = this.bindings[i];
  6535. var b = binding;
  6536. b.target.removeEventListener(b.event, b.handler, b.useCapture);
  6537. }
  6538. };
  6539. CanvasRenderer.prototype.png = function(){
  6540. var data = this.data;
  6541. // Rasterize the layers, but only if container has nonzero size
  6542. if (this.data.container.clientHeight > 0
  6543. && this.data.container.clientWidth > 0) {
  6544. context = data.bufferCanvases[1].getContext("2d");
  6545. context.globalCompositeOperation = "copy";
  6546. context.drawImage(data.canvases[4], 0, 0);
  6547. context.globalCompositeOperation = "source-over";
  6548. context.drawImage(data.canvases[2], 0, 0);
  6549. context.drawImage(data.canvases[0], 0, 0);
  6550. context = data.bufferCanvases[0].getContext("2d");
  6551. context.globalCompositeOperation = "copy";
  6552. context.drawImage(data.bufferCanvases[1], 0, 0);
  6553. }
  6554. var canvas = this.data.bufferCanvases[0];
  6555. return canvas.toDataURL("image/png");
  6556. };
  6557. // @O Initialization functions
  6558. {
  6559. CanvasRenderer.prototype.load = function() {
  6560. var r = this;
  6561. // helper function to determine which child nodes and inner edges
  6562. // of a compound node to be dragged as well as the grabbed and selected nodes
  6563. var addDescendantsToDrag = function(node, addSelected, dragElements) {
  6564. if (!addSelected)
  6565. {
  6566. var parents = node.parents();
  6567. // do not process descendants for this node,
  6568. // because those will be handled for the topmost selected parent
  6569. for (var i=0; i < parents.size(); i++)
  6570. {
  6571. if (parents[i]._private.selected)
  6572. {
  6573. return;
  6574. }
  6575. }
  6576. }
  6577. var innerNodes = node.descendants();
  6578. function hasNonAutoParent(ele){
  6579. while( ele.parent().nonempty() && ele.parent().id() !== node.id() ){
  6580. parent = ele.parent()[0];
  6581. var pstyle = parent._private.style;
  6582. if( pstyle.width.value !== 'auto' || pstyle.height.value !== 'auto' ){
  6583. return true;
  6584. }
  6585. ele = ele.parent();
  6586. }
  6587. return false;
  6588. }
  6589. // TODO do not drag hidden children & children of hidden children?
  6590. for (var i=0; i < innerNodes.size(); i++)
  6591. {
  6592. // if addSelected is true, then add node in any case,
  6593. // if not, then add only non-selected nodes
  6594. if ( (addSelected || !innerNodes[i]._private.selected) )
  6595. {
  6596. innerNodes[i]._private.rscratch.inDragLayer = true;
  6597. //innerNodes[i].trigger(new $$.Event(e, {type: "grab"}));
  6598. //innerNodes[i].trigger(event);
  6599. dragElements.push(innerNodes[i]);
  6600. for (var j=0; j < innerNodes[i]._private.edges.length; j++)
  6601. {
  6602. innerNodes[i]._private.edges[j]._private.rscratch.inDragLayer = true;
  6603. }
  6604. }
  6605. }
  6606. };
  6607. // adds the given nodes, and its edges to the drag layer
  6608. var addNodeToDrag = function(node, dragElements) {
  6609. node._private.grabbed = true;
  6610. node._private.rscratch.inDragLayer = true;
  6611. dragElements.push(node);
  6612. for (var i=0;i<node._private.edges.length;i++) {
  6613. node._private.edges[i]._private.rscratch.inDragLayer = true;
  6614. }
  6615. //node.trigger(new $$.Event(e, {type: "grab"}));
  6616. };
  6617. // helper function to determine which ancestor nodes and edges should go
  6618. // to the drag layer (or should be removed from drag layer).
  6619. var updateAncestorsInDragLayer = function(node, inDragLayer) {
  6620. // find top-level parent
  6621. var parent = node;
  6622. while (parent.parent().nonempty())
  6623. {
  6624. parent = parent.parent()[0];
  6625. // var pstyle = parent._private.style;
  6626. // if( pstyle.width.value !== 'auto' || pstyle.height.value !== 'auto' ){
  6627. // parent = node;
  6628. // break;
  6629. // }
  6630. }
  6631. // no parent node: no node to add to the drag layer
  6632. if (parent == node && inDragLayer)
  6633. {
  6634. return;
  6635. }
  6636. var nodes = parent.descendants().add(parent);
  6637. for (var i=0; i < nodes.size(); i++)
  6638. {
  6639. nodes[i]._private.rscratch.inDragLayer = inDragLayer;
  6640. // TODO when calling this function for a set of nodes, we visit same edges over and over again,
  6641. // instead of adding edges for each node, it may be better to iterate all edges at once
  6642. // or another solution is to find out the common ancestors and process only those nodes for edges
  6643. for (var j=0; j<nodes[i]._private.edges.length; j++) {
  6644. nodes[i]._private.edges[j]._private.rscratch.inDragLayer = inDragLayer;
  6645. }
  6646. }
  6647. };
  6648. CanvasRenderer.prototype.nodeIsDraggable = function(node) {
  6649. if (node._private.style["opacity"].value != 0
  6650. && node._private.style["visibility"].value == "visible"
  6651. && !node._private.locked
  6652. && node._private.grabbable) {
  6653. return true;
  6654. }
  6655. return false;
  6656. }
  6657. // auto resize
  6658. r.registerBinding(window, "resize", function(e) {
  6659. r.data.canvasNeedsRedraw[NODE] = true;
  6660. r.data.canvasNeedsRedraw[OVERLAY] = true;
  6661. r.matchCanvasSize( r.data.container );
  6662. r.redraw();
  6663. }, true);
  6664. // stop right click menu from appearing on cy
  6665. r.registerBinding(r.data.container, "contextmenu", function(e){
  6666. e.preventDefault();
  6667. });
  6668. // Primary key
  6669. r.registerBinding(r.data.container, "mousedown", function(e) {
  6670. e.preventDefault();
  6671. r.hoverData.capture = true;
  6672. r.hoverData.which = e.which;
  6673. var cy = r.data.cy; var pos = r.projectIntoViewport(e.pageX, e.pageY);
  6674. var select = r.data.select;
  6675. var near = r.findNearestElement(pos[0], pos[1], true);
  6676. var down = r.hoverData.down;
  6677. var draggedElements = r.dragData.possibleDragElements;
  6678. var grabEvent = new $$.Event(e, {type: "grab"});
  6679. // Right click button
  6680. if( e.which == 3 ){
  6681. if( near ){
  6682. near.activate();
  6683. near.trigger( new $$.Event(e, {type: "cxttapstart"}) );
  6684. r.hoverData.down = near;
  6685. r.hoverData.downTime = (new Date()).getTime();
  6686. r.hoverData.cxtDragged = false;
  6687. }
  6688. // Primary button
  6689. } else if (e.which == 1) {
  6690. if( near ){
  6691. near.activate();
  6692. }
  6693. // Element dragging
  6694. {
  6695. // If something is under the cursor and it is draggable, prepare to grab it
  6696. if (near != null && r.nodeIsDraggable(near)) {
  6697. if (near._private.group == "nodes" && near._private.selected == false) {
  6698. draggedElements = r.dragData.possibleDragElements = [ ];
  6699. addNodeToDrag(near, draggedElements);
  6700. near.trigger(grabEvent);
  6701. // add descendant nodes only if the compound size is set to auto
  6702. if (near._private.style["width"].value == "auto" ||
  6703. near._private.style["height"].value == "auto")
  6704. {
  6705. addDescendantsToDrag(near,
  6706. true,
  6707. draggedElements);
  6708. }
  6709. // also add nodes and edges related to the topmost ancestor
  6710. updateAncestorsInDragLayer(near, true);
  6711. }
  6712. if (near._private.group == "nodes" && near._private.selected == true) {
  6713. draggedElements = r.dragData.possibleDragElements = [ ];
  6714. var triggeredGrab = false;
  6715. var selectedNodes = cy.$('node:selected');
  6716. for( var i = 0; i < selectedNodes.length; i++ ){
  6717. //r.dragData.possibleDragElements.push( selectedNodes[i] );
  6718. // Only add this selected node to drag if it is draggable, eg. has nonzero opacity
  6719. if (r.nodeIsDraggable(selectedNodes[i]))
  6720. {
  6721. addNodeToDrag(selectedNodes[i], draggedElements);
  6722. // only trigger for grabbed node once
  6723. if( !triggeredGrab ){
  6724. near.trigger(grabEvent);
  6725. triggeredGrab = true;
  6726. }
  6727. if (selectedNodes[i]._private.style["width"].value == "auto" ||
  6728. selectedNodes[i]._private.style["height"].value == "auto")
  6729. {
  6730. addDescendantsToDrag(selectedNodes[i],
  6731. false,
  6732. draggedElements);
  6733. }
  6734. // also add nodes and edges related to the topmost ancestor
  6735. updateAncestorsInDragLayer(selectedNodes[i], true);
  6736. }
  6737. }
  6738. }
  6739. near
  6740. .trigger(new $$.Event(e, {type: "mousedown"}))
  6741. .trigger(new $$.Event(e, {type: "tapstart"}))
  6742. .trigger(new $$.Event(e, {type: "vmousedown"}))
  6743. ;
  6744. // r.data.canvasNeedsRedraw[DRAG] = true; r.data.canvasRedrawReason[DRAG].push("Single node moved to drag layer");
  6745. // r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("Single node moved to drag layer");
  6746. } else if (near == null) {
  6747. cy
  6748. .trigger(new $$.Event(e, {type: "mousedown"}))
  6749. .trigger(new $$.Event(e, {type: "tapstart"}))
  6750. .trigger(new $$.Event(e, {type: "vmousedown"}))
  6751. ;
  6752. }
  6753. r.hoverData.down = near;
  6754. r.hoverData.downTime = (new Date()).getTime();
  6755. }
  6756. // Selection box
  6757. if ( near == null || near.isEdge() ) {
  6758. select[4] = 1;
  6759. var timeUntilActive = Math.max( 0, panOrBoxSelectDelay - (+new Date - r.hoverData.downTime) );
  6760. clearTimeout( r.bgActiveTimeout );
  6761. r.bgActiveTimeout = setTimeout(function(){
  6762. if( near ){
  6763. near.unactivate();
  6764. }
  6765. r.data.bgActivePosistion = {
  6766. x: pos[0],
  6767. y: pos[1]
  6768. };
  6769. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  6770. r.data.canvasRedrawReason[SELECT_BOX].push("bgactive");
  6771. r.redraw();
  6772. }, timeUntilActive);
  6773. }
  6774. }
  6775. // Initialize selection box coordinates
  6776. select[0] = select[2] = pos[0];
  6777. select[1] = select[3] = pos[1];
  6778. r.redraw();
  6779. }, false);
  6780. r.registerBinding(window, "mousemove", function(e) {
  6781. var preventDefault = false;
  6782. var capture = r.hoverData.capture;
  6783. if (!capture) {
  6784. var containerPageCoords = r.findContainerPageCoords();
  6785. if (e.pageX > containerPageCoords[0] && e.pageX < containerPageCoords[0] + r.data.container.clientWidth
  6786. && e.pageY > containerPageCoords[1] && e.pageY < containerPageCoords[1] + r.data.container.clientHeight) {
  6787. } else {
  6788. return;
  6789. }
  6790. }
  6791. var cy = r.data.cy;
  6792. var pos = r.projectIntoViewport(e.pageX, e.pageY);
  6793. var select = r.data.select;
  6794. var near = r.findNearestElement(pos[0], pos[1], true);
  6795. var last = r.hoverData.last;
  6796. var down = r.hoverData.down;
  6797. var disp = [pos[0] - select[2], pos[1] - select[3]];
  6798. var nodes = r.getCachedNodes();
  6799. var edges = r.getCachedEdges();
  6800. var draggedElements = r.dragData.possibleDragElements;
  6801. var shiftDown = e.shiftKey;
  6802. preventDefault = true;
  6803. // Mousemove event
  6804. {
  6805. var event = new $$.Event(e, {type: "mousemove"});
  6806. if (near != null) {
  6807. near.trigger(event);
  6808. } else if (near == null) {
  6809. cy.trigger(event);
  6810. }
  6811. }
  6812. // trigger context drag if rmouse down
  6813. if( r.hoverData.which === 3 ){
  6814. var cxtEvt = new $$.Event(e, {type: "cxtdrag"});
  6815. if( down ){
  6816. down.trigger( cxtEvt );
  6817. } else {
  6818. cy.trigger( cxtEvt );
  6819. }
  6820. r.hoverData.cxtDragged = true;
  6821. // Check if we are drag panning the entire graph
  6822. } else if (r.hoverData.dragging) {
  6823. preventDefault = true;
  6824. if( cy.panningEnabled() ){
  6825. var deltaP = {x: disp[0] * cy.zoom(), y: disp[1] * cy.zoom()};
  6826. cy.panBy( deltaP );
  6827. }
  6828. // Needs reproject due to pan changing viewport
  6829. pos = r.projectIntoViewport(e.pageX, e.pageY);
  6830. // Checks primary button down & out of time & mouse not moved much
  6831. } else if (select[4] == 1 && (down == null || down.isEdge())
  6832. && ( !cy.boxSelectionEnabled() || +new Date - r.hoverData.downTime >= panOrBoxSelectDelay )
  6833. && (Math.abs(select[3] - select[1]) + Math.abs(select[2] - select[0]) < 4)
  6834. && cy.panningEnabled() ) {
  6835. r.hoverData.dragging = true;
  6836. select[4] = 0;
  6837. } else {
  6838. // deactivate bg on box selection
  6839. if (cy.boxSelectionEnabled() && Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 7 && select[4]){
  6840. clearTimeout( r.bgActiveTimeout );
  6841. }
  6842. if( down && down.isEdge() && down.active() ){ down.unactivate(); }
  6843. if (near != last) {
  6844. if (last) { last.trigger(new $$.Event(e, {type: "mouseout"})); }
  6845. if (near) { near.trigger(new $$.Event(e, {type: "mouseover"})); }
  6846. r.hoverData.last = near;
  6847. }
  6848. if ( down && down.isNode() && r.nodeIsDraggable(down) ) {
  6849. r.dragData.didDrag = true; // indicate that we actually did drag the node
  6850. var toTrigger = [];
  6851. for (var i=0; i<draggedElements.length; i++) {
  6852. // Locked nodes not draggable, as well as non-visible nodes
  6853. if (draggedElements[i]._private.group == "nodes"
  6854. && r.nodeIsDraggable(draggedElements[i])) {
  6855. draggedElements[i]._private.position.x += disp[0];
  6856. draggedElements[i]._private.position.y += disp[1];
  6857. toTrigger.push( draggedElements[i] );
  6858. }
  6859. }
  6860. (new $$.Collection(cy, toTrigger))
  6861. .trigger( new $$.Event(e, {type: "drag"}) )
  6862. .trigger( new $$.Event(e, {type: "position"}) )
  6863. ;
  6864. if (select[2] == select[0] && select[3] == select[1]) {
  6865. r.data.canvasNeedsRedraw[NODE] = true;
  6866. r.data.canvasRedrawReason[NODE].push("Node(s) and edge(s) moved to drag layer");
  6867. }
  6868. r.data.canvasNeedsRedraw[DRAG] = true;
  6869. r.data.canvasRedrawReason[DRAG].push("Nodes dragged");
  6870. }
  6871. if( cy.boxSelectionEnabled() ){
  6872. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  6873. r.data.canvasRedrawReason[SELECT_BOX].push("Mouse moved, redraw selection box");
  6874. }
  6875. // prevent the dragging from triggering text selection on the page
  6876. preventDefault = true;
  6877. }
  6878. select[2] = pos[0]; select[3] = pos[1];
  6879. r.redraw();
  6880. if( preventDefault ){
  6881. if(e.stopPropagation) e.stopPropagation();
  6882. if(e.preventDefault) e.preventDefault();
  6883. e.cancelBubble=true;
  6884. e.returnValue=false;
  6885. return false;
  6886. }
  6887. }, false);
  6888. r.registerBinding(window, "mouseup", function(e) {
  6889. // console.log('--\nmouseup', e)
  6890. var capture = r.hoverData.capture; if (!capture) { return; }; r.hoverData.capture = false;
  6891. var cy = r.data.cy; var pos = r.projectIntoViewport(e.pageX, e.pageY); var select = r.data.select;
  6892. var near = r.findNearestElement(pos[0], pos[1], true);
  6893. var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
  6894. var draggedElements = r.dragData.possibleDragElements; var down = r.hoverData.down;
  6895. var shiftDown = e.shiftKey;
  6896. r.data.bgActivePosistion = undefined; // not active bg now
  6897. clearTimeout( r.bgActiveTimeout );
  6898. if( down ){
  6899. down.unactivate();
  6900. }
  6901. if( r.hoverData.which === 3 ){
  6902. var cxtEvt = new $$.Event(e, {type: "cxttapend"});
  6903. if( down ){
  6904. down.trigger( cxtEvt );
  6905. } else {
  6906. cy.trigger( cxtEvt );
  6907. }
  6908. if( !r.hoverData.cxtDragged ){
  6909. var cxtTap = new $$.Event(e, {type: "cxttap"});
  6910. if( down ){
  6911. down.trigger( cxtTap );
  6912. } else {
  6913. cy.trigger( cxtTap );
  6914. }
  6915. }
  6916. r.hoverData.cxtDragged = false;
  6917. r.hoverData.which = null;
  6918. // if not right mouse
  6919. } else {
  6920. // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something
  6921. if ( (down == null) // not mousedown on node
  6922. && !r.dragData.didDrag // didn't move the node around
  6923. && !(Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 7 && select[4]) // not box selection
  6924. && !r.hoverData.dragging // not panning
  6925. ) {
  6926. // console.log('unselect all from bg');
  6927. //++clock+unselect
  6928. // var a = time();
  6929. cy.$(':selected').unselect();
  6930. //++clock+unselect
  6931. // console.log("unselect", time() - a);
  6932. if (draggedElements.length > 0) {
  6933. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("De-select");
  6934. }
  6935. r.dragData.possibleDragElements = draggedElements = [];
  6936. }
  6937. // Click event
  6938. {
  6939. // console.log('trigger click et al');
  6940. if (Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) == 0) {
  6941. if (near != null) {
  6942. near
  6943. .trigger( new $$.Event(e, {type: "click"}) )
  6944. .trigger( new $$.Event(e, {type: "tap"}) )
  6945. .trigger( new $$.Event(e, {type: "vclick"}) )
  6946. ;
  6947. } else if (near == null) {
  6948. cy
  6949. .trigger( new $$.Event(e, {type: "click"}) )
  6950. .trigger( new $$.Event(e, {type: "tap"}) )
  6951. .trigger( new $$.Event(e, {type: "vclick"}) )
  6952. ;
  6953. }
  6954. }
  6955. }
  6956. // Mouseup event
  6957. {
  6958. // console.log('trigger mouseup et al');
  6959. if (near != null) {
  6960. near
  6961. .trigger(new $$.Event(e, {type: "mouseup"}))
  6962. .trigger(new $$.Event(e, {type: "tapend"}))
  6963. .trigger(new $$.Event(e, {type: "vmouseup"}))
  6964. ;
  6965. } else if (near == null) {
  6966. cy
  6967. .trigger(new $$.Event(e, {type: "mouseup"}))
  6968. .trigger(new $$.Event(e, {type: "tapend"}))
  6969. .trigger(new $$.Event(e, {type: "vmouseup"}))
  6970. ;
  6971. }
  6972. }
  6973. // Single selection
  6974. if (near == down && !r.dragData.didDrag) {
  6975. if (near != null && near._private.selectable) {
  6976. // console.log('single selection')
  6977. if( !shiftDown ){
  6978. cy.$(':selected').unselect();
  6979. }
  6980. if( near.selected() ){
  6981. near.unselect();
  6982. } else {
  6983. near.select();
  6984. }
  6985. updateAncestorsInDragLayer(near, false);
  6986. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("sglslct");
  6987. }
  6988. // Ungrab single drag
  6989. } else if (near == down) {
  6990. if (near != null && near._private.grabbed) {
  6991. // console.log('ungrab single drag')
  6992. var grabbedEles = cy.$(':grabbed');
  6993. for(var i = 0; i < grabbedEles.length; i++){
  6994. var ele = grabbedEles[i];
  6995. ele._private.grabbed = false;
  6996. var sEdges = ele._private.edges;
  6997. for (var j=0;j<sEdges.length;j++) { sEdges[j]._private.rscratch.inDragLayer = false; }
  6998. // for compound nodes, also remove related nodes and edges from the drag layer
  6999. updateAncestorsInDragLayer(ele, false);
  7000. }
  7001. var freeEvent = new $$.Event(e, {type: "free"});
  7002. grabbedEles.trigger(freeEvent);
  7003. }
  7004. }
  7005. if ( cy.boxSelectionEnabled() && Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 7 && select[4] ) {
  7006. // console.log("box selection");
  7007. if( !shiftDown ){
  7008. cy.$(':selected').unselect();
  7009. }
  7010. var newlySelected = [];
  7011. var box = r.getAllInBox(select[0], select[1], select[2], select[3]);
  7012. // console.log(box);
  7013. var event = new $$.Event(e, {type: "select"});
  7014. for (var i=0;i<box.length;i++) {
  7015. if (box[i]._private.selectable) {
  7016. draggedElements.push( box[i] );
  7017. newlySelected.push( box[i] );
  7018. }
  7019. }
  7020. (new $$.Collection( cy, newlySelected )).select();
  7021. if (box.length > 0) {
  7022. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("Selection");
  7023. }
  7024. }
  7025. // Cancel drag pan
  7026. r.hoverData.dragging = false;
  7027. if (!select[4]) {
  7028. // console.log('free at end', draggedElements)
  7029. var freeEvent = new $$.Event(e, {type: "free"});
  7030. for (var i=0;i<draggedElements.length;i++) {
  7031. if (draggedElements[i]._private.group == "nodes") {
  7032. draggedElements[i]._private.rscratch.inDragLayer = false;
  7033. var sEdges = draggedElements[i]._private.edges;
  7034. for (var j=0;j<sEdges.length;j++) { sEdges[j]._private.rscratch.inDragLayer = false; }
  7035. // for compound nodes, also remove related nodes and edges from the drag layer
  7036. updateAncestorsInDragLayer(draggedElements[i], false);
  7037. } else if (draggedElements[i]._private.group == "edges") {
  7038. draggedElements[i]._private.rscratch.inDragLayer = false;
  7039. }
  7040. }
  7041. if( down){ down.trigger(freeEvent); }
  7042. // draggedElements = r.dragData.possibleDragElements = [];
  7043. r.data.canvasNeedsRedraw[DRAG] = true; r.data.canvasRedrawReason[DRAG].push("Node/nodes back from drag");
  7044. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("Node/nodes back from drag");
  7045. }
  7046. } // else not right mouse
  7047. select[4] = 0; r.hoverData.down = null;
  7048. r.data.canvasNeedsRedraw[SELECT_BOX] = true; r.data.canvasRedrawReason[SELECT_BOX].push("Mouse up, selection box gone");
  7049. // console.log("mu", pos[0], pos[1]);
  7050. // console.log("ss", select);
  7051. r.dragData.didDrag = false;
  7052. r.redraw();
  7053. }, false);
  7054. var wheelHandler = function(e) {
  7055. var cy = r.data.cy; var pos = r.projectIntoViewport(e.pageX, e.pageY);
  7056. var unpos = [pos[0] * cy.zoom() + cy.pan().x,
  7057. pos[1] * cy.zoom() + cy.pan().y];
  7058. if (r.zoomData.freeToZoom) {
  7059. e.preventDefault();
  7060. var diff = e.wheelDeltaY / 1000 || e.detail / -32;
  7061. if( cy.panningEnabled() && cy.zoomingEnabled() ){
  7062. cy.zoom({level: cy.zoom() * Math.pow(10, diff), position: {x: unpos[0], y: unpos[1]}});
  7063. }
  7064. r.data.wheel = true;
  7065. clearTimeout(r.data.wheelTimeout);
  7066. r.data.wheelTimeout = setTimeout(function(){
  7067. r.data.wheel = false;
  7068. r.data.canvasNeedsRedraw[NODE] = true;
  7069. r.redraw();
  7070. }, 100);
  7071. }
  7072. }
  7073. // Functions to help with whether mouse wheel should trigger zooming
  7074. // --
  7075. r.registerBinding(r.data.container, "mousewheel", wheelHandler, true);
  7076. r.registerBinding(r.data.container, "DOMMouseScroll", wheelHandler, true);
  7077. r.registerBinding(r.data.container, "MozMousePixelScroll", function(e){
  7078. if (r.zoomData.freeToZoom) {
  7079. e.preventDefault();
  7080. }
  7081. }, false);
  7082. r.registerBinding(r.data.container, "mousemove", function(e) {
  7083. if (r.zoomData.lastPointerX && r.zoomData.lastPointerX != e.pageX && !r.zoomData.freeToZoom)
  7084. { r.zoomData.freeToZoom = true; } r.zoomData.lastPointerX = e.pageX;
  7085. }, false);
  7086. r.registerBinding(r.data.container, "mouseout", function(e) {
  7087. r.zoomData.freeToZoom = false; r.zoomData.lastPointerX = null
  7088. }, false);
  7089. // --
  7090. // Functions to help with handling mouseout/mouseover on the Cytoscape container
  7091. // Handle mouseout on Cytoscape container
  7092. r.registerBinding(r.data.container, "mouseout", function(e) {
  7093. r.data.cy.trigger(new $$.Event(e, {type: "mouseout"}));
  7094. }, false);
  7095. r.registerBinding(r.data.container, "mouseover", function(e) {
  7096. r.data.cy.trigger(new $$.Event(e, {type: "mouseover"}));
  7097. }, false);
  7098. var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom
  7099. var distance1; // initial distance between finger 1 and finger 2 for pinch-to-zoom
  7100. var center1, modelCenter1; // center point on start pinch to zoom
  7101. var offsetLeft, offsetTop;
  7102. var containerWidth = r.data.container.clientWidth, containerHeight = r.data.container.clientHeight;
  7103. var twoFingersStartInside;
  7104. function distance(x1, y1, x2, y2){
  7105. return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
  7106. }
  7107. r.registerBinding(r.data.container, "touchstart", function(e) {
  7108. if( e.target !== r.data.link ){
  7109. e.preventDefault();
  7110. }
  7111. r.touchData.capture = true;
  7112. r.data.bgActivePosistion = undefined;
  7113. var cy = r.data.cy;
  7114. var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
  7115. var now = r.touchData.now; var earlier = r.touchData.earlier;
  7116. if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].pageX, e.touches[0].pageY); now[0] = pos[0]; now[1] = pos[1]; }
  7117. if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].pageX, e.touches[1].pageY); now[2] = pos[0]; now[3] = pos[1]; }
  7118. if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].pageX, e.touches[2].pageY); now[4] = pos[0]; now[5] = pos[1]; }
  7119. // record starting points for pinch-to-zoom
  7120. if( e.touches[1] ){
  7121. // anything in the set of dragged eles should be released
  7122. function release( eles ){
  7123. for( var i = 0; i < eles.length; i++ ){
  7124. eles[i]._private.grabbed = false;
  7125. eles[i]._private.rscratch.inDragLayer = false;
  7126. if( eles[i].active() ){ eles[i].unactivate(); }
  7127. }
  7128. }
  7129. release(nodes);
  7130. release(edges);
  7131. var offsets = r.findContainerPageCoords();
  7132. offsetTop = offsets[1];
  7133. offsetLeft = offsets[0];
  7134. f1x1 = e.touches[0].pageX - offsetLeft;
  7135. f1y1 = e.touches[0].pageY - offsetTop;
  7136. f2x1 = e.touches[1].pageX - offsetLeft;
  7137. f2y1 = e.touches[1].pageY - offsetTop;
  7138. twoFingersStartInside =
  7139. 0 <= f1x1 && f1x1 <= containerWidth
  7140. && 0 <= f2x1 && f2x1 <= containerWidth
  7141. && 0 <= f1y1 && f1y1 <= containerHeight
  7142. && 0 <= f2y1 && f2y1 <= containerHeight
  7143. ;
  7144. var pan = cy.pan();
  7145. var zoom = cy.zoom();
  7146. distance1 = distance( f1x1, f1y1, f2x1, f2y1 );
  7147. center1 = [ (f1x1 + f2x1)/2, (f1y1 + f2y1)/2 ];
  7148. modelCenter1 = [
  7149. (center1[0] - pan.x) / zoom,
  7150. (center1[1] - pan.y) / zoom
  7151. ];
  7152. // consider context tap
  7153. if( distance1 < 100 ){
  7154. var near1 = r.findNearestElement(now[0], now[1], true);
  7155. var near2 = r.findNearestElement(now[2], now[3], true);
  7156. var cxtEvt = new $$.Event(e, {type: "cxttapstart"});
  7157. //console.log(distance1)
  7158. if( near1 && near1.isNode() ){
  7159. near1.activate().trigger( cxtEvt );
  7160. r.touchData.start = near1;
  7161. } else if( near2 && near2.isNode() ){
  7162. near2.activate().trigger( cxtEvt );
  7163. r.touchData.start = near2;
  7164. } else {
  7165. cy.trigger( cxtEvt );
  7166. r.touchData.start = null;
  7167. }
  7168. if( r.touchData.start ){ r.touchData.start._private.grabbed = false; }
  7169. r.touchData.cxt = true;
  7170. r.touchData.cxtDragged = false;
  7171. r.data.bgActivePosistion = undefined;
  7172. //console.log('cxttapstart')
  7173. r.redraw();
  7174. return;
  7175. }
  7176. // console.log(center1);
  7177. // console.log('touchstart ptz');
  7178. // console.log(offsetLeft, offsetTop);
  7179. // console.log(f1x1, f1y1);
  7180. // console.log(f2x1, f2y1);
  7181. // console.log(distance1);
  7182. // console.log(center1);
  7183. }
  7184. // console.log('another tapstart')
  7185. if (e.touches[2]) {
  7186. } else if (e.touches[1]) {
  7187. } else if (e.touches[0]) {
  7188. var near = r.findNearestElement(now[0], now[1], true);
  7189. if (near != null) {
  7190. near.activate();
  7191. r.touchData.start = near;
  7192. if (near._private.group == "nodes" && r.nodeIsDraggable(near))
  7193. {
  7194. var draggedEles = r.dragData.touchDragEles = [];
  7195. addNodeToDrag(near, draggedEles);
  7196. near.trigger(new $$.Event(e, {type: "grab"}));
  7197. if( near.selected() ){
  7198. // reset drag elements, since near will be added again
  7199. draggedEles = r.dragData.touchDragEles = [];
  7200. var selectedNodes = cy.$('node:selected');
  7201. for( var k = 0; k < selectedNodes.length; k++ ){
  7202. var selectedNode = selectedNodes[k];
  7203. if (r.nodeIsDraggable(selectedNode)) {
  7204. draggedEles.push( selectedNode );
  7205. selectedNode._private.rscratch.inDragLayer = true;
  7206. var sEdges = selectedNode._private.edges;
  7207. for (var j=0; j<sEdges.length; j++) {
  7208. sEdges[j]._private.rscratch.inDragLayer = true;
  7209. }
  7210. if (selectedNode._private.style["width"].value == "auto" ||
  7211. selectedNode._private.style["height"].value == "auto")
  7212. {
  7213. addDescendantsToDrag(selectedNode,
  7214. false,
  7215. draggedEles);
  7216. }
  7217. // also add nodes and edges related to the topmost ancestor
  7218. updateAncestorsInDragLayer(selectedNode, true);
  7219. }
  7220. }
  7221. } else {
  7222. //draggedEles.push( near );
  7223. // add descendant nodes only if the compound size is set to auto
  7224. if (near._private.style["width"].value == "auto" ||
  7225. near._private.style["height"].value == "auto")
  7226. {
  7227. addDescendantsToDrag(near,
  7228. true,
  7229. draggedEles);
  7230. }
  7231. // also add nodes and edges related to the topmost ancestor
  7232. updateAncestorsInDragLayer(near, true);
  7233. }
  7234. }
  7235. near
  7236. .trigger(new $$.Event(e, {type: "touchstart"}))
  7237. .trigger(new $$.Event(e, {type: "tapstart"}))
  7238. .trigger(new $$.Event(e, {type: "vmousdown"}))
  7239. ;
  7240. } if (near == null) {
  7241. cy
  7242. .trigger(new $$.Event(e, {type: "touchstart"}))
  7243. .trigger(new $$.Event(e, {type: "tapstart"}))
  7244. .trigger(new $$.Event(e, {type: "vmousedown"}))
  7245. ;
  7246. r.data.bgActivePosistion = {
  7247. x: pos[0],
  7248. y: pos[1]
  7249. };
  7250. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7251. r.data.canvasRedrawReason[SELECT_BOX].push("bgactive");
  7252. }
  7253. // Tap, taphold
  7254. // -----
  7255. for (var i=0;i<now.length;i++) {
  7256. earlier[i] = now[i];
  7257. r.touchData.startPosition[i] = now[i];
  7258. };
  7259. r.touchData.singleTouchMoved = false;
  7260. r.touchData.singleTouchStartTime = time();
  7261. var tapHoldTimeout = setTimeout(function() {
  7262. if (r.touchData.singleTouchMoved == false
  7263. // This time double constraint prevents multiple quick taps
  7264. // followed by a taphold triggering multiple taphold events
  7265. && time() - r.touchData.singleTouchStartTime > 250) {
  7266. if (r.touchData.start) {
  7267. r.touchData.start.trigger(new $$.Event(e, {type: "taphold"}));
  7268. } else {
  7269. r.data.cy.trigger(new $$.Event(e, {type: "taphold"}));
  7270. cy.$(':selected').unselect();
  7271. }
  7272. // console.log("taphold");
  7273. }
  7274. }, 1000);
  7275. }
  7276. r.redraw();
  7277. }, false);
  7278. // console.log = function(m){ $('#console').append('<div>'+m+'</div>'); };
  7279. r.registerBinding(window, "touchmove", function(e) {
  7280. var select = r.data.select;
  7281. var capture = r.touchData.capture; //if (!capture) { return; };
  7282. capture && e.preventDefault();
  7283. var cy = r.data.cy;
  7284. var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
  7285. var now = r.touchData.now; var earlier = r.touchData.earlier;
  7286. if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].pageX, e.touches[0].pageY); now[0] = pos[0]; now[1] = pos[1]; }
  7287. if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].pageX, e.touches[1].pageY); now[2] = pos[0]; now[3] = pos[1]; }
  7288. if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].pageX, e.touches[2].pageY); now[4] = pos[0]; now[5] = pos[1]; }
  7289. var disp = []; for (var j=0;j<now.length;j++) { disp[j] = now[j] - earlier[j]; }
  7290. if( capture && r.touchData.cxt ){
  7291. var f1x2 = e.touches[0].pageX - offsetLeft, f1y2 = e.touches[0].pageY - offsetTop;
  7292. var f2x2 = e.touches[1].pageX - offsetLeft, f2y2 = e.touches[1].pageY - offsetTop;
  7293. var distance2 = distance( f1x2, f1y2, f2x2, f2y2 );
  7294. var factor = distance2 / distance1;
  7295. //console.log(factor, distance2)
  7296. // cancel ctx gestures if the distance b/t the fingers increases
  7297. if( factor >= 1.5 || distance2 >= 150 ){
  7298. r.touchData.cxt = false;
  7299. if( r.touchData.start ){ r.touchData.start.unactivate(); r.touchData.start = null; }
  7300. r.data.bgActivePosistion = undefined;
  7301. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7302. var cxtEvt = new $$.Event(e, {type: "cxttapend"});
  7303. if( r.touchData.start ){
  7304. r.touchData.start.trigger( cxtEvt );
  7305. } else {
  7306. cy.trigger( cxtEvt );
  7307. }
  7308. }
  7309. }
  7310. if( capture && r.touchData.cxt ){
  7311. var cxtEvt = new $$.Event(e, {type: "cxtdrag"});
  7312. r.data.bgActivePosistion = undefined;
  7313. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7314. if( r.touchData.start ){
  7315. r.touchData.start.trigger( cxtEvt );
  7316. } else {
  7317. cy.trigger( cxtEvt );
  7318. }
  7319. if( r.touchData.start ){ r.touchData.start._private.grabbed = false; }
  7320. r.touchData.cxtDragged = true;
  7321. //console.log('cxtdrag')
  7322. } else if( capture && e.touches[2] && cy.boxSelectionEnabled() ){
  7323. r.data.bgActivePosistion = undefined;
  7324. clearTimeout( this.threeFingerSelectTimeout );
  7325. this.lastThreeTouch = +new Date;
  7326. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7327. r.data.canvasRedrawReason[SELECT_BOX].push("Touch moved, redraw selection box");
  7328. if( !select || select.length === 0 || select[0] === undefined ){
  7329. select[0] = (now[0] + now[2] + now[4])/3;
  7330. select[1] = (now[1] + now[3] + now[5])/3;
  7331. select[2] = (now[0] + now[2] + now[4])/3 + 1;
  7332. select[3] = (now[1] + now[3] + now[5])/3 + 1;
  7333. } else {
  7334. select[2] = (now[0] + now[2] + now[4])/3;
  7335. select[3] = (now[1] + now[3] + now[5])/3;
  7336. }
  7337. select[4] = 1;
  7338. } else if ( capture && e.touches[1] && cy.zoomingEnabled() && cy.panningEnabled() ) { // two fingers => pinch to zoom
  7339. r.data.bgActivePosistion = undefined;
  7340. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7341. // console.log('touchmove ptz');
  7342. // (x2, y2) for fingers 1 and 2
  7343. var f1x2 = e.touches[0].pageX - offsetLeft, f1y2 = e.touches[0].pageY - offsetTop;
  7344. var f2x2 = e.touches[1].pageX - offsetLeft, f2y2 = e.touches[1].pageY - offsetTop;
  7345. // console.log( f1x2, f1y2 )
  7346. // console.log( f2x2, f2y2 )
  7347. var distance2 = distance( f1x2, f1y2, f2x2, f2y2 );
  7348. var factor = distance2 / distance1;
  7349. // console.log(distance2)
  7350. // console.log(factor)
  7351. if( factor != 1 && twoFingersStartInside){
  7352. // console.log(factor)
  7353. // console.log(distance2 + ' / ' + distance1);
  7354. // console.log('--');
  7355. // delta finger1
  7356. var df1x = f1x2 - f1x1;
  7357. var df1y = f1y2 - f1y1;
  7358. // delta finger 2
  7359. var df2x = f2x2 - f2x1;
  7360. var df2y = f2y2 - f2y1;
  7361. // translation is the normalised vector of the two fingers movement
  7362. // i.e. so pinching cancels out and moving together pans
  7363. var tx = (df1x + df2x)/2;
  7364. var ty = (df1y + df2y)/2;
  7365. // adjust factor by the speed multiplier
  7366. // var speed = 1.5;
  7367. // if( factor > 1 ){
  7368. // factor = (factor - 1) * speed + 1;
  7369. // } else {
  7370. // factor = 1 - (1 - factor) * speed;
  7371. // }
  7372. // now calculate the zoom
  7373. var zoom1 = cy.zoom();
  7374. var zoom2 = zoom1 * factor;
  7375. var pan1 = cy.pan();
  7376. // the model center point converted to the current rendered pos
  7377. var ctrx = modelCenter1[0] * zoom1 + pan1.x;
  7378. var ctry = modelCenter1[1] * zoom1 + pan1.y;
  7379. var pan2 = {
  7380. x: -zoom2/zoom1 * (ctrx - pan1.x - tx) + ctrx,
  7381. y: -zoom2/zoom1 * (ctry - pan1.y - ty) + ctry
  7382. };
  7383. // console.log(pan2);
  7384. // console.log(zoom2);
  7385. cy._private.zoom = zoom2;
  7386. cy._private.pan = pan2;
  7387. cy
  7388. .trigger('pan zoom')
  7389. .notify('viewport')
  7390. ;
  7391. distance1 = distance2;
  7392. f1x1 = f1x2;
  7393. f1y1 = f1y2;
  7394. f2x1 = f2x2;
  7395. f2y1 = f2y2;
  7396. r.pinching = true;
  7397. }
  7398. // Re-project
  7399. if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].pageX, e.touches[0].pageY); now[0] = pos[0]; now[1] = pos[1]; }
  7400. if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].pageX, e.touches[1].pageY); now[2] = pos[0]; now[3] = pos[1]; }
  7401. if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].pageX, e.touches[2].pageY); now[4] = pos[0]; now[5] = pos[1]; }
  7402. } else if (e.touches[0]) {
  7403. var start = r.touchData.start;
  7404. var last = r.touchData.last;
  7405. if ( start != null && start._private.group == "nodes" && r.nodeIsDraggable(start)) {
  7406. var draggedEles = r.dragData.touchDragEles;
  7407. for( var k = 0; k < draggedEles.length; k++ ){
  7408. var draggedEle = draggedEles[k];
  7409. if( r.nodeIsDraggable(draggedEle) ){
  7410. r.dragData.didDrag = true;
  7411. draggedEle._private.position.x += disp[0];
  7412. draggedEle._private.position.y += disp[1];
  7413. }
  7414. }
  7415. ( new $$.Collection(cy, draggedEles) )
  7416. .trigger( new $$.Event(e, {type: "drag"}) )
  7417. .trigger( new $$.Event(e, {type: "position"}) )
  7418. ;
  7419. r.data.canvasNeedsRedraw[DRAG] = true;
  7420. r.data.canvasRedrawReason[DRAG].push("touchdrag node");
  7421. if (r.touchData.startPosition[0] == earlier[0]
  7422. && r.touchData.startPosition[1] == earlier[1]) {
  7423. r.data.canvasNeedsRedraw[NODE] = true;
  7424. r.data.canvasRedrawReason[NODE].push("node drag started");
  7425. }
  7426. }
  7427. // Touchmove event
  7428. {
  7429. if (start != null) { start.trigger(new $$.Event(e, {type: "touchmove"})); }
  7430. if (start == null) {
  7431. var near = r.findNearestElement(now[0], now[1], true);
  7432. if (near != null) { near.trigger(new $$.Event(e, {type: "touchmove"})); }
  7433. if (near == null) { cy.trigger(new $$.Event(e, {type: "touchmove"})); }
  7434. }
  7435. if (near != last) {
  7436. if (last) { last.trigger(new $$.Event(e, {type: "touchout"})); }
  7437. if (near) { near.trigger(new $$.Event(e, {type: "touchover"})); }
  7438. }
  7439. r.touchData.last = near;
  7440. }
  7441. // Check to cancel taphold
  7442. for (var i=0;i<now.length;i++) {
  7443. if (now[i]
  7444. && r.touchData.startPosition[i]
  7445. && Math.abs(now[i] - r.touchData.startPosition[i]) > 4) {
  7446. r.touchData.singleTouchMoved = true;
  7447. }
  7448. }
  7449. if ( capture && (start == null || start.isEdge()) && cy.panningEnabled() ) {
  7450. if( start ){
  7451. start.unactivate();
  7452. if( !r.data.bgActivePosistion ){
  7453. r.data.bgActivePosistion = {
  7454. x: now[0],
  7455. y: now[1]
  7456. };
  7457. }
  7458. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7459. r.data.canvasRedrawReason[SELECT_BOX].push("bgactive");
  7460. }
  7461. cy.panBy({x: disp[0] * cy.zoom(), y: disp[1] * cy.zoom()});
  7462. r.swipePanning = true;
  7463. // Re-project
  7464. var pos = r.projectIntoViewport(e.touches[0].pageX, e.touches[0].pageY);
  7465. now[0] = pos[0]; now[1] = pos[1];
  7466. }
  7467. }
  7468. for (var j=0;j<now.length;j++) { earlier[j] = now[j]; };
  7469. r.redraw();
  7470. }, false);
  7471. r.registerBinding(window, "touchend", function(e) {
  7472. var capture = r.touchData.capture; if (!capture) { return; }; r.touchData.capture = false;
  7473. e.preventDefault();
  7474. var select = r.data.select;
  7475. r.swipePanning = false;
  7476. var cy = r.data.cy;
  7477. var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
  7478. var now = r.touchData.now; var earlier = r.touchData.earlier;
  7479. var start = r.touchData.start;
  7480. if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].pageX, e.touches[0].pageY); now[0] = pos[0]; now[1] = pos[1]; }
  7481. if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].pageX, e.touches[1].pageY); now[2] = pos[0]; now[3] = pos[1]; }
  7482. if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].pageX, e.touches[2].pageY); now[4] = pos[0]; now[5] = pos[1]; }
  7483. if( r.touchData.cxt ){
  7484. ctxTapend = new $$.Event(e, { type: 'cxttapend' });
  7485. if( start ){
  7486. start.unactivate();
  7487. start.trigger( ctxTapend );
  7488. } else {
  7489. cy.trigger( ctxTapend );
  7490. }
  7491. //console.log('cxttapend')
  7492. if( !r.touchData.cxtDragged ){
  7493. var ctxTap = new $$.Event(e, { type: 'cxttap' });
  7494. if( start ){
  7495. start.trigger( ctxTap );
  7496. } else {
  7497. cy.trigger( ctxTap );
  7498. }
  7499. //console.log('cxttap')
  7500. }
  7501. if( r.touchData.start ){ r.touchData.start._private.grabbed = false; }
  7502. r.touchData.cxt = false;
  7503. r.touchData.start = null;
  7504. r.redraw();
  7505. return;
  7506. }
  7507. var nowTime = +new Date;
  7508. // no more box selection if we don't have three fingers
  7509. if( !e.touches[2] && cy.boxSelectionEnabled() ){
  7510. clearTimeout( this.threeFingerSelectTimeout );
  7511. this.threeFingerSelectTimeout = setTimeout(function(){
  7512. var newlySelected = [];
  7513. var box = r.getAllInBox(select[0], select[1], select[2], select[3]);
  7514. select[0] = undefined;
  7515. select[1] = undefined;
  7516. select[2] = undefined;
  7517. select[3] = undefined;
  7518. select[4] = 0;
  7519. r.data.canvasNeedsRedraw[SELECT_BOX] = true;
  7520. r.data.canvasRedrawReason[SELECT_BOX].push("Touch moved, redraw selection box");
  7521. // console.log(box);
  7522. var event = new $$.Event(e, {type: "select"});
  7523. for (var i=0;i<box.length;i++) {
  7524. if (box[i]._private.selectable) {
  7525. newlySelected.push( box[i] );
  7526. }
  7527. }
  7528. (new $$.Collection( cy, newlySelected )).select();
  7529. if (box.length > 0) {
  7530. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("Selection");
  7531. }
  7532. }, 100);
  7533. }
  7534. if( !e.touches[1] ){
  7535. r.pinching = false;
  7536. }
  7537. var updateStartStyle = false;
  7538. if( start != null ){
  7539. start._private.active = false;
  7540. updateStartStyle = true;
  7541. start.trigger( new $$.Event(e, {type: "unactivate"}) );
  7542. }
  7543. if (e.touches[2]) {
  7544. r.data.bgActivePosistion = undefined;
  7545. } else if (e.touches[1]) {
  7546. } else if (e.touches[0]) {
  7547. // Last touch released
  7548. } else if (!e.touches[0]) {
  7549. r.data.bgActivePosistion = undefined;
  7550. if (start != null ) {
  7551. if (start._private.grabbed == true) {
  7552. start._private.grabbed = false;
  7553. start.trigger(new $$.Event(e, {type: "free"}));
  7554. start._private.rscratch.inDragLayer = false;
  7555. }
  7556. var sEdges = start._private.edges;
  7557. for (var j=0;j<sEdges.length;j++) { sEdges[j]._private.rscratch.inDragLayer = false; }
  7558. updateAncestorsInDragLayer(start, false);
  7559. if( start.selected() ){
  7560. var selectedNodes = cy.$('node:selected');
  7561. for( var k = 0; k < selectedNodes.length; k++ ){
  7562. var selectedNode = selectedNodes[k];
  7563. selectedNode._private.rscratch.inDragLayer = false;
  7564. var sEdges = selectedNode._private.edges;
  7565. for (var j=0; j<sEdges.length; j++) {
  7566. sEdges[j]._private.rscratch.inDragLayer = false;
  7567. }
  7568. updateAncestorsInDragLayer(selectedNode, false);
  7569. }
  7570. }
  7571. r.data.canvasNeedsRedraw[DRAG] = true; r.data.canvasRedrawReason[DRAG].push("touchdrag node end");
  7572. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("touchdrag node end");
  7573. start
  7574. .trigger(new $$.Event(e, {type: "touchend"}))
  7575. .trigger(new $$.Event(e, {type: "tapend"}))
  7576. .trigger(new $$.Event(e, {type: "vmouseup"}))
  7577. ;
  7578. r.touchData.start = null;
  7579. } else {
  7580. var near = r.findNearestElement(now[0], now[1], true);
  7581. if (near != null) {
  7582. near
  7583. .trigger(new $$.Event(e, {type: "touchend"}))
  7584. .trigger(new $$.Event(e, {type: "tapend"}))
  7585. .trigger(new $$.Event(e, {type: "vmouseup"}))
  7586. ;
  7587. }
  7588. if (near == null) {
  7589. cy
  7590. .trigger(new $$.Event(e, {type: "touchend"}))
  7591. .trigger(new $$.Event(e, {type: "tapend"}))
  7592. .trigger(new $$.Event(e, {type: "vmouseup"}))
  7593. ;
  7594. }
  7595. }
  7596. // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance
  7597. if (start != null
  7598. && !r.dragData.didDrag // didn't drag nodes around
  7599. && start._private.selectable
  7600. && (Math.sqrt(Math.pow(r.touchData.startPosition[0] - now[0], 2) + Math.pow(r.touchData.startPosition[1] - now[1], 2))) < 6) {
  7601. if( start.selected() ){
  7602. start._private.selected = false;
  7603. start.trigger( new $$.Event(e, {type: "unselect"}) );
  7604. } else {
  7605. start._private.selected = true;
  7606. start.trigger( new $$.Event(e, {type: "select"}) );
  7607. }
  7608. updateStartStyle = true;
  7609. r.data.canvasNeedsRedraw[NODE] = true; r.data.canvasRedrawReason[NODE].push("sglslct");
  7610. }
  7611. // Tap event, roughly same as mouse click event for touch
  7612. if (r.touchData.singleTouchMoved == false) {
  7613. if (start) {
  7614. start
  7615. .trigger(new $$.Event(e, {type: "tap"}))
  7616. .trigger(new $$.Event(e, {type: "vclick"}))
  7617. ;
  7618. } else {
  7619. cy
  7620. .trigger(new $$.Event(e, {type: "tap"}))
  7621. .trigger(new $$.Event(e, {type: "vclick"}))
  7622. ;
  7623. }
  7624. // console.log("tap");
  7625. }
  7626. r.touchData.singleTouchMoved = true;
  7627. }
  7628. for (var j=0;j<now.length;j++) { earlier[j] = now[j]; };
  7629. r.dragData.didDrag = false; // reset for next mousedown
  7630. if( updateStartStyle && start ){
  7631. start.updateStyle(false);
  7632. }
  7633. r.redraw();
  7634. }, false);
  7635. };
  7636. CanvasRenderer.prototype.init = function() { };
  7637. }
  7638. // @O Caching functions
  7639. {
  7640. CanvasRenderer.prototype.getCachedNodes = function() {
  7641. var data = this.data; var cy = this.data.cy;
  7642. if (data.cache == undefined) {
  7643. data.cache = {};
  7644. }
  7645. if (data.cache.cachedNodes == undefined) {
  7646. data.cache.cachedNodes = cy.nodes();
  7647. }
  7648. return data.cache.cachedNodes;
  7649. }
  7650. CanvasRenderer.prototype.updateNodesCache = function() {
  7651. var data = this.data; var cy = this.data.cy;
  7652. if (data.cache == undefined) {
  7653. data.cache = {};
  7654. }
  7655. data.cache.cachedNodes = cy.nodes();
  7656. }
  7657. CanvasRenderer.prototype.getCachedEdges = function() {
  7658. var data = this.data; var cy = this.data.cy;
  7659. if (data.cache == undefined) {
  7660. data.cache = {};
  7661. }
  7662. if (data.cache.cachedEdges == undefined) {
  7663. data.cache.cachedEdges = cy.edges();
  7664. }
  7665. return data.cache.cachedEdges;
  7666. }
  7667. CanvasRenderer.prototype.updateEdgesCache = function() {
  7668. var data = this.data; var cy = this.data.cy;
  7669. if (data.cache == undefined) {
  7670. data.cache = {};
  7671. }
  7672. data.cache.cachedEdges = cy.edges();
  7673. }
  7674. }
  7675. // @O High-level collision application functions
  7676. // Project mouse
  7677. CanvasRenderer.prototype.projectIntoViewport = function(pageX, pageY) {
  7678. n = this.data.container;
  7679. // Stop checking scroll past the level of the DOM tree containing document.body. At this point, scroll values do not have the same impact on pageX/pageY.
  7680. var stopCheckingScroll = false;
  7681. var offsets = this.findContainerPageCoords();
  7682. var offsetLeft = offsets[0];
  7683. var offsetTop = offsets[1];
  7684. // console.log("calce");
  7685. // By here, offsetLeft and offsetTop represent the "pageX/pageY" of the top-left corner of the div. So, do subtraction to find relative position.
  7686. x = pageX - offsetLeft; y = pageY - offsetTop;
  7687. x -= this.data.cy.pan().x; y -= this.data.cy.pan().y; x /= this.data.cy.zoom(); y /= this.data.cy.zoom();
  7688. return [x, y];
  7689. }
  7690. CanvasRenderer.prototype.findContainerPageCoords = function() {
  7691. var x, y; var offsetLeft = 0; var offsetTop = 0; var n; n = this.data.container;
  7692. // Stop checking scroll past the level of the DOM tree containing document.body. At this point, scroll values do not have the same impact on pageX/pageY.
  7693. var stopCheckingScroll = false;
  7694. while (n != null) {
  7695. var style = window.getComputedStyle(n);
  7696. if( style.getPropertyValue('position').toLowerCase() === 'fixed' ){
  7697. offsetLeft = n.offsetLeft + window.scrollX;
  7698. offsetTop = n.offsetTop + window.scrollY;
  7699. n = null; // don't want to check any more parents after position:fixed
  7700. } else if (typeof(n.offsetLeft) == "number") {
  7701. // The idea is to add offsetLeft/offsetTop, subtract scrollLeft/scrollTop, ignoring scroll values for elements in DOM tree levels 2 and higher.
  7702. offsetLeft += n.offsetLeft; offsetTop += n.offsetTop;
  7703. if (n == document.body || n == document.header) { stopCheckingScroll = true; }
  7704. if (!stopCheckingScroll) { offsetLeft -= n.scrollLeft; offsetTop -= n.scrollTop; }
  7705. }
  7706. if( n ){ n = n.offsetParent };
  7707. }
  7708. // By here, offsetLeft and offsetTop represent the "pageX/pageY" of the top-left corner of the div.
  7709. return [offsetLeft, offsetTop];
  7710. }
  7711. // Find nearest element
  7712. CanvasRenderer.prototype.findNearestElement = function(x, y, visibleElementsOnly) {
  7713. var data = this.data; var nodes = this.getCachedNodes(); var edges = this.getCachedEdges(); var near = [];
  7714. var zoom = this.data.cy.zoom();
  7715. var edgeThreshold = (isTouch ? 256 : 32) / zoom;
  7716. var nodeThreshold = (isTouch ? 16 : 0) / zoom;
  7717. // Check nodes
  7718. for (var i = 0; i < nodes.length; i++) {
  7719. if (nodeShapes[this.getNodeShape(nodes[i])].checkPointRough(x, y,
  7720. nodes[i]._private.style["border-width"].value / 2,
  7721. //nodes[i]._private.style["width"].value, nodes[i]._private.style["height"].value,
  7722. this.getNodeWidth(nodes[i]) + nodeThreshold, this.getNodeHeight(nodes[i]) + nodeThreshold,
  7723. nodes[i]._private.position.x, nodes[i]._private.position.y)
  7724. &&
  7725. nodeShapes[this.getNodeShape(nodes[i])].checkPoint(x, y,
  7726. nodes[i]._private.style["border-width"].value / 2,
  7727. //nodes[i]._private.style["width"].value / 2, nodes[i]._private.style["height"].value / 2,
  7728. (this.getNodeWidth(nodes[i]) + nodeThreshold), (this.getNodeHeight(nodes[i]) + nodeThreshold),
  7729. nodes[i]._private.position.x, nodes[i]._private.position.y)) {
  7730. if (visibleElementsOnly) {
  7731. if (nodes[i]._private.style["opacity"].value != 0
  7732. && nodes[i]._private.style["visibility"].value == "visible") {
  7733. near.push(nodes[i]);
  7734. }
  7735. } else {
  7736. near.push(nodes[i]);
  7737. }
  7738. }
  7739. }
  7740. // Check edges
  7741. var addCurrentEdge;
  7742. for (var i = 0; i < edges.length; i++) {
  7743. var edge = edges[i];
  7744. var rs = edge._private.rscratch;
  7745. addCurrentEdge = false;
  7746. if (rs.edgeType == "self") {
  7747. if ((this.inBezierVicinity(x, y,
  7748. rs.startX,
  7749. rs.startY,
  7750. rs.cp2ax,
  7751. rs.cp2ay,
  7752. rs.selfEdgeMidX,
  7753. rs.selfEdgeMidY,
  7754. Math.pow(edge._private.style["width"].value/2, 2))
  7755. &&
  7756. (Math.pow(edges[i]._private.style["width"].value/2, 2) + edgeThreshold >
  7757. this.sqDistanceToQuadraticBezier(x, y,
  7758. rs.startX,
  7759. rs.startY,
  7760. rs.cp2ax,
  7761. rs.cp2ay,
  7762. rs.selfEdgeMidX,
  7763. rs.selfEdgeMidY)))
  7764. ||
  7765. (this.inBezierVicinity(x, y,
  7766. rs.selfEdgeMidX,
  7767. rs.selfEdgeMidY,
  7768. rs.cp2cx,
  7769. rs.cp2cy,
  7770. rs.endX,
  7771. rs.endY,
  7772. Math.pow(edges[i]._private.style["width"].value/2, 2))
  7773. &&
  7774. (Math.pow(edges[i]._private.style["width"].value/2, 2) + edgeThreshold >
  7775. this.sqDistanceToQuadraticBezier(x, y,
  7776. rs.selfEdgeMidX,
  7777. rs.selfEdgeMidY,
  7778. rs.cp2cx,
  7779. rs.cp2cy,
  7780. rs.endX,
  7781. rs.endY))))
  7782. { addCurrentEdge = true; }
  7783. } else if (rs.edgeType == "straight") {
  7784. if (this.inLineVicinity(x, y, rs.startX, rs.startY, rs.endX, rs.endY, edges[i]._private.style["width"].value * 2)
  7785. &&
  7786. Math.pow(edges[i]._private.style["width"].value / 2, 2) + edgeThreshold >
  7787. this.sqDistanceToFiniteLine(x, y,
  7788. rs.startX,
  7789. rs.startY,
  7790. rs.endX,
  7791. rs.endY))
  7792. { addCurrentEdge = true; }
  7793. } else if (rs.edgeType == "bezier") {
  7794. if (this.inBezierVicinity(x, y,
  7795. rs.startX,
  7796. rs.startY,
  7797. rs.cp2x,
  7798. rs.cp2y,
  7799. rs.endX,
  7800. rs.endY,
  7801. Math.pow(edges[i]._private.style["width"].value / 2, 2))
  7802. &&
  7803. (Math.pow(edges[i]._private.style["width"].value / 2 , 2) + edgeThreshold >
  7804. this.sqDistanceToQuadraticBezier(x, y,
  7805. rs.startX,
  7806. rs.startY,
  7807. rs.cp2x,
  7808. rs.cp2y,
  7809. rs.endX,
  7810. rs.endY)))
  7811. { addCurrentEdge = true; }
  7812. }
  7813. if (!near.length || near[near.length - 1] != edges[i]) {
  7814. if ((arrowShapes[edges[i]._private.style["source-arrow-shape"].value].roughCollide(x, y,
  7815. edges[i]._private.rscratch.arrowStartX, edges[i]._private.rscratch.arrowStartY,
  7816. this.getArrowWidth(edges[i]._private.style["width"].value),
  7817. this.getArrowHeight(edges[i]._private.style["width"].value),
  7818. [edges[i]._private.rscratch.arrowStartX - edges[i].source()[0]._private.position.x,
  7819. edges[i]._private.rscratch.arrowStartY - edges[i].source()[0]._private.position.y], 0)
  7820. &&
  7821. arrowShapes[edges[i]._private.style["source-arrow-shape"].value].collide(x, y,
  7822. edges[i]._private.rscratch.arrowStartX, edges[i]._private.rscratch.arrowStartY,
  7823. this.getArrowWidth(edges[i]._private.style["width"].value),
  7824. this.getArrowHeight(edges[i]._private.style["width"].value),
  7825. [edges[i]._private.rscratch.arrowStartX - edges[i].source()[0]._private.position.x,
  7826. edges[i]._private.rscratch.arrowStartY - edges[i].source()[0]._private.position.y], 0))
  7827. ||
  7828. (arrowShapes[edges[i]._private.style["target-arrow-shape"].value].roughCollide(x, y,
  7829. edges[i]._private.rscratch.arrowEndX, edges[i]._private.rscratch.arrowEndY,
  7830. this.getArrowWidth(edges[i]._private.style["width"].value),
  7831. this.getArrowHeight(edges[i]._private.style["width"].value),
  7832. [edges[i]._private.rscratch.arrowEndX - edges[i].target()[0]._private.position.x,
  7833. edges[i]._private.rscratch.arrowEndY - edges[i].target()[0]._private.position.y], 0)
  7834. &&
  7835. arrowShapes[edges[i]._private.style["target-arrow-shape"].value].collide(x, y,
  7836. edges[i]._private.rscratch.arrowEndX, edges[i]._private.rscratch.arrowEndY,
  7837. this.getArrowWidth(edges[i]._private.style["width"].value),
  7838. this.getArrowHeight(edges[i]._private.style["width"].value),
  7839. [edges[i]._private.rscratch.arrowEndX - edges[i].target()[0]._private.position.x,
  7840. edges[i]._private.rscratch.arrowEndY - edges[i].target()[0]._private.position.y], 0)))
  7841. { addCurrentEdge = true; }
  7842. }
  7843. if (addCurrentEdge) {
  7844. if (visibleElementsOnly) {
  7845. // For edges, make sure the edge is visible/has nonzero opacity,
  7846. // then also make sure both source and target nodes are visible/have
  7847. // nonzero opacity
  7848. var source = data.cy.getElementById(edges[i]._private.data.source)
  7849. var target = data.cy.getElementById(edges[i]._private.data.target)
  7850. if (edges[i]._private.style["opacity"].value != 0
  7851. && edges[i]._private.style["visibility"].value == "visible"
  7852. && source._private.style["opacity"].value != 0
  7853. && source._private.style["visibility"].value == "visible"
  7854. && target._private.style["opacity"].value != 0
  7855. && target._private.style["visibility"].value == "visible") {
  7856. near.push(edges[i]);
  7857. }
  7858. } else {
  7859. near.push(edges[i]);
  7860. }
  7861. }
  7862. }
  7863. near.sort( zOrderSort );
  7864. if (near.length > 0) { return near[ near.length - 1 ]; } else { return null; }
  7865. }
  7866. // "Give me everything from this box"
  7867. CanvasRenderer.prototype.getAllInBox = function(x1, y1, x2, y2) {
  7868. var data = this.data; var nodes = this.getCachedNodes(); var edges = this.getCachedEdges(); var box = [];
  7869. var x1c = Math.min(x1, x2); var x2c = Math.max(x1, x2); var y1c = Math.min(y1, y2); var y2c = Math.max(y1, y2); x1 = x1c; x2 = x2c; y1 = y1c; y2 = y2c; var heur;
  7870. for (var i=0;i<nodes.length;i++) {
  7871. if (nodeShapes[this.getNodeShape(nodes[i])].intersectBox(x1, y1, x2, y2,
  7872. //nodes[i]._private.style["width"].value, nodes[i]._private.style["height"].value,
  7873. this.getNodeWidth(nodes[i]), this.getNodeHeight(nodes[i]),
  7874. nodes[i]._private.position.x, nodes[i]._private.position.y, nodes[i]._private.style["border-width"].value / 2))
  7875. { box.push(nodes[i]); }
  7876. }
  7877. for (var i=0;i<edges.length;i++) {
  7878. if (edges[i]._private.rscratch.edgeType == "self") {
  7879. if ((heur = this.boxInBezierVicinity(x1, y1, x2, y2,
  7880. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7881. edges[i]._private.rscratch.cp2ax, edges[i]._private.rscratch.cp2ay,
  7882. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))
  7883. &&
  7884. (heur == 2 || (heur == 1 && this.checkBezierInBox(x1, y1, x2, y2,
  7885. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7886. edges[i]._private.rscratch.cp2ax, edges[i]._private.rscratch.cp2ay,
  7887. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value)))
  7888. ||
  7889. (heur = this.boxInBezierVicinity(x1, y1, x2, y2,
  7890. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7891. edges[i]._private.rscratch.cp2cx, edges[i]._private.rscratch.cp2cy,
  7892. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))
  7893. &&
  7894. (heur == 2 || (heur == 1 && this.checkBezierInBox(x1, y1, x2, y2,
  7895. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7896. edges[i]._private.rscratch.cp2cx, edges[i]._private.rscratch.cp2cy,
  7897. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value)))
  7898. )
  7899. { box.push(edges[i]); }
  7900. }
  7901. if (edges[i]._private.rscratch.edgeType == "bezier" &&
  7902. (heur = this.boxInBezierVicinity(x1, y1, x2, y2,
  7903. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7904. edges[i]._private.rscratch.cp2x, edges[i]._private.rscratch.cp2y,
  7905. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))
  7906. &&
  7907. (heur == 2 || (heur == 1 && this.checkBezierInBox(x1, y1, x2, y2,
  7908. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7909. edges[i]._private.rscratch.cp2x, edges[i]._private.rscratch.cp2y,
  7910. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))))
  7911. { box.push(edges[i]); }
  7912. if (edges[i]._private.rscratch.edgeType == "straight" &&
  7913. (heur = this.boxInBezierVicinity(x1, y1, x2, y2,
  7914. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7915. edges[i]._private.rscratch.startX * 0.5 + edges[i]._private.rscratch.endX * 0.5,
  7916. edges[i]._private.rscratch.startY * 0.5 + edges[i]._private.rscratch.endY * 0.5,
  7917. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))
  7918. && /* console.log("test", heur) == undefined && */
  7919. (heur == 2 || (heur == 1 && this.checkStraightEdgeInBox(x1, y1, x2, y2,
  7920. edges[i]._private.rscratch.startX, edges[i]._private.rscratch.startY,
  7921. edges[i]._private.rscratch.endX, edges[i]._private.rscratch.endY, edges[i]._private.style["width"].value))))
  7922. { box.push(edges[i]); }
  7923. }
  7924. return box;
  7925. }
  7926. /**
  7927. * Updates bounds of all compounds in the given element list.
  7928. * Assuming the nodes are sorted top down, i.e. a parent node
  7929. * always has a lower index than its all children.
  7930. *
  7931. * @param elements set of elements containing both nodes and edges
  7932. */
  7933. CanvasRenderer.prototype.updateAllCompounds = function(elements)
  7934. {
  7935. // traverse in reverse order, since rendering is top-down,
  7936. // but we need to calculate bounds bottom-up
  7937. for(var i = elements.length - 1; i >= 0; i--)
  7938. {
  7939. if (elements[i].isNode() &&
  7940. (elements[i]._private.style["width"].value == "auto" ||
  7941. elements[i]._private.style["height"].value == "auto") &&
  7942. elements[i].children().length > 0)
  7943. {
  7944. var node = elements[i];
  7945. var bounds = this.calcCompoundBounds(node);
  7946. //console.log("%s : %o", node._private.data.id, bounds);
  7947. node._private.position.x = bounds.x;
  7948. node._private.position.y = bounds.y;
  7949. node._private.autoWidth = bounds.width;
  7950. node._private.autoHeight = bounds.height;
  7951. }
  7952. }
  7953. };
  7954. /**
  7955. * Calculates rectangular bounds of a given compound node.
  7956. * If the node is hidden, or none of its children is visible,
  7957. * then instead of calculating the bounds, returns the last
  7958. * calculated value.
  7959. *
  7960. * @param node a node with children (compound node)
  7961. * @return {{x: number, y: number, width: number, height: number}}
  7962. */
  7963. CanvasRenderer.prototype.calcCompoundBounds = function(node)
  7964. {
  7965. // TODO assuming rectangular compounds, we may add support for other shapes in the future
  7966. // this selection doesn't work if parent is invisible
  7967. //var children = node.children(":visible").not(":removed");
  7968. // consider only not removed children
  7969. var children = node.descendants().not(":removed");
  7970. // TODO instead of last calculated width & height define a default compound node size?
  7971. // last calculated bounds
  7972. var bounds = {x: node._private.position.x,
  7973. y: node._private.position.y,
  7974. width: node._private.autoWidth,
  7975. height: node._private.autoHeight};
  7976. // check node visibility
  7977. if (node._private.style["visibility"].value != "visible")
  7978. {
  7979. // do not calculate bounds for invisible compounds,
  7980. // just return last calculated values
  7981. return bounds;
  7982. }
  7983. var visibleChildren = [];
  7984. // find out visible children
  7985. for (var i=0; i < children.size(); i++)
  7986. {
  7987. if (children[i]._private.style["visibility"].value == "visible")
  7988. {
  7989. visibleChildren.push(children[i]);
  7990. }
  7991. }
  7992. if (visibleChildren.length == 0)
  7993. {
  7994. // no visible children, just return last calculated values
  7995. return bounds;
  7996. }
  7997. // process only visible children
  7998. children = visibleChildren;
  7999. // find the leftmost, rightmost, topmost, and bottommost child node positions
  8000. var leftBorder = this.borderValue(children, "left");
  8001. var rightBorder = this.borderValue(children, "right");
  8002. var topBorder = this.borderValue(children, "top");
  8003. var bottomBorder = this.borderValue(children, "bottom");
  8004. // take padding values into account in addition to border values
  8005. var padding = this.getNodePadding(node);
  8006. var x = (leftBorder - padding.left + rightBorder + padding.right) / 2;
  8007. var y = (topBorder - padding.top + bottomBorder + padding.bottom) / 2;
  8008. var width = (rightBorder - leftBorder) + padding.left + padding.right;
  8009. var height = (bottomBorder - topBorder) + padding.top + padding.bottom;
  8010. // it is not possible to use the function boundingBox() before
  8011. // actually rendering the graph
  8012. // var bBox = children.boundingBox();
  8013. //
  8014. // var x = (bBox.x1 + bBox.x2) / 2;
  8015. // var y = (bBox.y1 + bBox.y2) / 2;
  8016. // var width = bBox.width;
  8017. // var height = bBox.height;
  8018. bounds = {x: x,
  8019. y: y,
  8020. width: width,
  8021. height: height};
  8022. return bounds;
  8023. };
  8024. /**
  8025. * Calculates the leftmost, rightmost, topmost or bottommost point for the given
  8026. * set of nodes. If the type parameter is "left" (or "right"), then the min (or
  8027. * the max) x-coordinate value will be returned. If the type is "top" (or "bottom")
  8028. * then the min (or the max) y-coordinate value will be returned.
  8029. *
  8030. * This function is designed to help determining the bounds (bounding box) of
  8031. * compound nodes.
  8032. *
  8033. * @param nodes set of nodes
  8034. * @param type "left", "right", "top", "bottom"
  8035. * @return {number} border value for the specified type
  8036. */
  8037. CanvasRenderer.prototype.borderValue = function(nodes, type)
  8038. {
  8039. var nodeVals, labelVals;
  8040. var minValue = 1/0, maxValue = -1/0;
  8041. var r = this;
  8042. // helper function to determine node position and dimensions
  8043. var calcNodePosAndDim = function(node) {
  8044. var values = {};
  8045. values.x = node._private.position.x;
  8046. values.y = node._private.position.y;
  8047. //values.width = r.getNodeWidth(node);
  8048. //values.height = r.getNodeHeight(node);
  8049. values.width = node.outerWidth();
  8050. values.height = node.outerHeight();
  8051. return values;
  8052. };
  8053. // helper function to determine label width
  8054. var getLabelWidth = function(node)
  8055. {
  8056. var text = String(node._private.style["content"].value);
  8057. var textTransform = node._private.style["text-transform"].value;
  8058. if (textTransform == "none") {
  8059. } else if (textTransform == "uppercase") {
  8060. text = text.toUpperCase();
  8061. } else if (textTransform == "lowercase") {
  8062. text = text.toLowerCase();
  8063. }
  8064. // TODO width doesn't measure correctly without actually rendering
  8065. var context = r.data.canvases[4].getContext("2d");
  8066. return context.measureText(text).width;
  8067. };
  8068. // helper function to determine label position and dimensions
  8069. var calcLabelPosAndDim = function(node) {
  8070. var values = {};
  8071. var nodeWidth = r.getNodeWidth(node);
  8072. var nodeHeight = r.getNodeHeight(node);
  8073. values.height = node._private.style["font-size"].value;
  8074. // TODO ignoring label width for now, it may be a good idea to do so,
  8075. // since longer label texts may increase the node size unnecessarily
  8076. //values.width = getLabelWidth(node);
  8077. values.width = values.height;
  8078. var textHalign = node._private.style["text-halign"].strValue;
  8079. if (textHalign == "left") {
  8080. values.x = node._private.position.x - nodeWidth / 2;
  8081. values.left = values.x - values.width;
  8082. values.right = values.x;
  8083. } else if (textHalign == "right") {
  8084. values.x = node._private.position.x + nodeWidth / 2;
  8085. values.left = values.x;
  8086. values.right = values.x + values.width;
  8087. } else { //if (textHalign == "center")
  8088. values.x = node._private.position.x;
  8089. values.left = values.x - values.width / 2;
  8090. values.right = values.x + values.width / 2;
  8091. }
  8092. var textValign = node._private.style["text-valign"].strValue;
  8093. if (textValign == "top") {
  8094. values.y = node._private.position.y - nodeHeight / 2;
  8095. values.top = values.y - values.height;
  8096. values.bottom = values.y;
  8097. } else if (textValign == "bottom") {
  8098. values.y = node._private.position.y + nodeHeight / 2;
  8099. values.top = values.y;
  8100. values.bottom = values.y + values.height;
  8101. } else { // if (textValign == "middle" || textValign == "center")
  8102. values.y = node._private.position.y;
  8103. values.top = values.y - values.height / 2;
  8104. values.bottom = values.y + values.height / 2;
  8105. }
  8106. return values;
  8107. };
  8108. // find out border values by iterating given nodes
  8109. for (i = 0; i < nodes.length; i++)
  8110. {
  8111. nodeVals = calcNodePosAndDim(nodes[i]);
  8112. labelVals = calcLabelPosAndDim(nodes[i]);
  8113. if (type == "left")
  8114. {
  8115. var leftBorder = Math.min(nodeVals.x - nodeVals.width / 2,
  8116. labelVals.left);
  8117. if (leftBorder < minValue)
  8118. {
  8119. minValue = leftBorder;
  8120. }
  8121. }
  8122. else if (type == "right")
  8123. {
  8124. var rightBorder = Math.max(nodeVals.x + nodeVals.width / 2,
  8125. labelVals.right);
  8126. if (rightBorder > maxValue)
  8127. {
  8128. maxValue = rightBorder;
  8129. }
  8130. }
  8131. else if (type == "top")
  8132. {
  8133. var topBorder = Math.min(nodeVals.y - nodeVals.height / 2,
  8134. labelVals.top);
  8135. if (topBorder < minValue)
  8136. {
  8137. minValue = topBorder;
  8138. }
  8139. }
  8140. else if (type == "bottom")
  8141. {
  8142. var bottomBorder = Math.max(nodeVals.y + nodeVals.height / 2,
  8143. labelVals.bottom);
  8144. if (bottomBorder > maxValue)
  8145. {
  8146. maxValue = bottomBorder;
  8147. }
  8148. }
  8149. }
  8150. // return the border value according to the type
  8151. if ((type == "left") || (type == "top"))
  8152. {
  8153. return minValue;
  8154. }
  8155. else
  8156. {
  8157. return maxValue;
  8158. }
  8159. };
  8160. /**
  8161. * Returns the width of the given node. If the width is set to auto,
  8162. * returns the value of the autoWidth field.
  8163. *
  8164. * @param node a node
  8165. * @return {number} width of the node
  8166. */
  8167. CanvasRenderer.prototype.getNodeWidth = function(node)
  8168. {
  8169. if (node._private.style["width"].value == "auto" ||
  8170. node._private.style["height"].value == "auto")
  8171. {
  8172. return node._private.autoWidth;
  8173. }
  8174. else
  8175. {
  8176. return node._private.style["width"].value;
  8177. }
  8178. };
  8179. /**
  8180. * Returns the height of the given node. If the height is set to auto,
  8181. * returns the value of the autoHeight field.
  8182. *
  8183. * @param node a node
  8184. * @return {number} width of the node
  8185. */
  8186. CanvasRenderer.prototype.getNodeHeight = function(node)
  8187. {
  8188. if (node._private.style["width"].value == "auto" ||
  8189. node._private.style["height"].value == "auto")
  8190. {
  8191. return node._private.autoHeight;
  8192. }
  8193. else
  8194. {
  8195. return node._private.style["height"].value;
  8196. }
  8197. };
  8198. /**
  8199. * Returns the shape of the given node. If the height or width of the given node
  8200. * is set to auto, the node is considered to be a compound.
  8201. *
  8202. * @param node a node
  8203. * @return {String} shape of the node
  8204. */
  8205. CanvasRenderer.prototype.getNodeShape = function(node)
  8206. {
  8207. // TODO only allow rectangle for a compound node?
  8208. // if (node._private.style["width"].value == "auto" ||
  8209. // node._private.style["height"].value == "auto")
  8210. // {
  8211. // return "rectangle";
  8212. // }
  8213. var shape = node._private.style["shape"].value;
  8214. if( node.isParent() ){
  8215. if( shape === 'rectangle' || shape === 'roundrectangle' ){
  8216. return shape;
  8217. } else {
  8218. return 'rectangle';
  8219. }
  8220. }
  8221. return shape;
  8222. };
  8223. CanvasRenderer.prototype.getNodePadding = function(node)
  8224. {
  8225. var left = node._private.style["padding-left"].value;
  8226. var right = node._private.style["padding-right"].value;
  8227. var top = node._private.style["padding-top"].value;
  8228. var bottom = node._private.style["padding-bottom"].value;
  8229. if (isNaN(left))
  8230. {
  8231. left = 0;
  8232. }
  8233. if (isNaN(right))
  8234. {
  8235. right = 0;
  8236. }
  8237. if (isNaN(top))
  8238. {
  8239. top = 0;
  8240. }
  8241. if (isNaN(bottom))
  8242. {
  8243. bottom = 0;
  8244. }
  8245. return {left : left,
  8246. right : right,
  8247. top : top,
  8248. bottom : bottom};
  8249. };
  8250. // @O Keyboard functions
  8251. {
  8252. }
  8253. // @O Drawing functions
  8254. {
  8255. // Resize canvas
  8256. CanvasRenderer.prototype.matchCanvasSize = function(container) {
  8257. var data = this.data; var width = container.clientWidth; var height = container.clientHeight;
  8258. var canvas, canvasWidth = width, canvasHeight = height;
  8259. if ('devicePixelRatio' in window) {
  8260. canvasWidth *= devicePixelRatio;
  8261. canvasHeight *= devicePixelRatio;
  8262. }
  8263. for (var i = 0; i < CANVAS_LAYERS; i++) {
  8264. canvas = data.canvases[i];
  8265. if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
  8266. canvas.width = canvasWidth;
  8267. canvas.height = canvasHeight;
  8268. canvas.style.width = width + 'px';
  8269. canvas.style.height = height + 'px';
  8270. }
  8271. }
  8272. for (var i = 0; i < BUFFER_COUNT; i++) {
  8273. canvas = data.bufferCanvases[i];
  8274. if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
  8275. canvas.width = canvasWidth;
  8276. canvas.height = canvasHeight;
  8277. }
  8278. }
  8279. this.data.overlay.style.width = width + 'px';
  8280. this.data.overlay.style.height = height + 'px';
  8281. }
  8282. // helper function for the sort operation
  8283. var elementDepth = function(ele) {
  8284. if (ele._private.group == "nodes")
  8285. {
  8286. return ele.parents().size();
  8287. }
  8288. else if (ele._private.group == "edges")
  8289. {
  8290. return Math.max(ele.source()[0].parents().size(),
  8291. ele.target()[0].parents().size());
  8292. }
  8293. else
  8294. {
  8295. return 0;
  8296. }
  8297. };
  8298. CanvasRenderer.prototype.getCachedZSortedEles = function(){
  8299. var lastNodes = this.lastZOrderCachedNodes;
  8300. var lastEdges = this.lastZOrderCachedEdges;
  8301. var nodes = this.getCachedNodes();
  8302. var edges = this.getCachedEdges();
  8303. var eles = [];
  8304. if( !lastNodes || !lastEdges || lastNodes !== nodes || lastEdges !== edges ){
  8305. //console.time('cachezorder')
  8306. for( var i = 0; i < nodes.length; i++ ){
  8307. eles.push( nodes[i] );
  8308. }
  8309. for( var i = 0; i < edges.length; i++ ){
  8310. eles.push( edges[i] );
  8311. }
  8312. eles.sort( zOrderSort );
  8313. this.cachedZSortedEles = eles;
  8314. //console.log('make cache')
  8315. //console.timeEnd('cachezorder')
  8316. } else {
  8317. eles = this.cachedZSortedEles;
  8318. //console.log('read cache')
  8319. }
  8320. this.lastZOrderCachedNodes = nodes;
  8321. this.lastZOrderCachedEdges = edges;
  8322. return eles;
  8323. };
  8324. var zOrderSort = function(a, b) {
  8325. var result = a._private.style["z-index"].value
  8326. - b._private.style["z-index"].value;
  8327. var depthA = 0;
  8328. var depthB = 0;
  8329. // no need to calculate element depth if there is no compound node
  8330. if ( a.cy().hasCompoundNodes() )
  8331. {
  8332. depthA = elementDepth(a);
  8333. depthB = elementDepth(b);
  8334. }
  8335. // if both elements has same depth,
  8336. // then edges should be drawn first
  8337. if (depthA - depthB === 0)
  8338. {
  8339. // "a" is a node, it should be drawn later
  8340. if (a._private.group === "nodes"
  8341. && b._private.group === "edges")
  8342. {
  8343. return 1;
  8344. }
  8345. // "a" is an edge, it should be drawn first
  8346. else if (a._private.group === "edges"
  8347. && b._private.group === "nodes")
  8348. {
  8349. return -1;
  8350. }
  8351. // both nodes or both edges
  8352. else
  8353. {
  8354. if( result === 0 ){ // same z-index => compare indices in the core (order added to graph w/ last on top)
  8355. return a._private.index - b._private.index;
  8356. } else {
  8357. return result;
  8358. }
  8359. }
  8360. }
  8361. // elements on different level
  8362. else
  8363. {
  8364. // deeper element should be drawn later
  8365. return depthA - depthB;
  8366. }
  8367. // return zero if z-index values are not the same
  8368. return 0;
  8369. };
  8370. CanvasRenderer.prototype.renderTo = function( cxt, zoom, pan ){
  8371. this.redraw( cxt, true, zoom, pan );
  8372. };
  8373. // Redraw frame
  8374. CanvasRenderer.prototype.redraw = function( forcedContext, drawAll, forcedZoom, forcedPan ) {
  8375. var r = this;
  8376. if( this.averageRedrawTime === undefined ){ this.averageRedrawTime = 0; }
  8377. var minRedrawLimit = 1000/60; // people can't see much better than 60fps
  8378. var maxRedrawLimit = 1000; // don't cap max b/c it's more important to be responsive than smooth
  8379. var redrawLimit = this.averageRedrawTime; // estimate the ideal redraw limit based on how fast we can draw
  8380. redrawLimit = Math.max(minRedrawLimit, redrawLimit);
  8381. redrawLimit = Math.min(redrawLimit, maxRedrawLimit);
  8382. //console.log('--\nideal: %i; effective: %i', this.averageRedrawTime, redrawLimit);
  8383. if( this.lastDrawTime === undefined ){ this.lastDrawTime = 0; }
  8384. var nowTime = +new Date;
  8385. var timeElapsed = nowTime - this.lastDrawTime;
  8386. var callAfterLimit = timeElapsed >= redrawLimit;
  8387. if( !forcedContext ){
  8388. if( !callAfterLimit ){
  8389. clearTimeout( this.redrawTimeout );
  8390. this.redrawTimeout = setTimeout(function(){
  8391. r.redraw();
  8392. }, redrawLimit);
  8393. return;
  8394. }
  8395. this.lastDrawTime = nowTime;
  8396. }
  8397. // start on thread ready
  8398. setTimeout(function(){
  8399. var startTime = nowTime;
  8400. var looperMax = 100;
  8401. //console.log('-- redraw --')
  8402. // console.time('init'); for( var looper = 0; looper <= looperMax; looper++ ){
  8403. var cy = r.data.cy; var data = r.data;
  8404. var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
  8405. r.matchCanvasSize(data.container);
  8406. var zoom = cy.zoom();
  8407. var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom;
  8408. var pan = cy.pan();
  8409. var effectivePan = {
  8410. x: pan.x,
  8411. y: pan.y
  8412. };
  8413. if( forcedPan ){
  8414. effectivePan = forcedPan;
  8415. }
  8416. if( 'devicePixelRatio' in window ){
  8417. effectiveZoom *= devicePixelRatio;
  8418. effectivePan.x *= devicePixelRatio;
  8419. effectivePan.y *= devicePixelRatio;
  8420. }
  8421. var elements = [];
  8422. for( var i = 0; i < nodes.length; i++ ){
  8423. elements.push( nodes[i] );
  8424. }
  8425. for( var i = 0; i < edges.length; i++ ){
  8426. elements.push( edges[i] );
  8427. }
  8428. // } console.timeEnd('init')
  8429. if (data.canvasNeedsRedraw[DRAG] || data.canvasNeedsRedraw[NODE] || drawAll) {
  8430. //NB : VERY EXPENSIVE
  8431. //console.time('edgectlpts'); for( var looper = 0; looper <= looperMax; looper++ ){
  8432. if( r.hideEdgesOnViewport && (r.pinching || r.hoverData.dragging || r.data.wheel || r.swipePanning) ){
  8433. } else {
  8434. r.findEdgeControlPoints(edges);
  8435. }
  8436. //} console.timeEnd('edgectlpts')
  8437. // console.time('sort'); for( var looper = 0; looper <= looperMax; looper++ ){
  8438. var elements = r.getCachedZSortedEles();
  8439. // } console.timeEnd('sort')
  8440. // console.time('updatecompounds'); for( var looper = 0; looper <= looperMax; looper++ ){
  8441. // no need to update graph if there is no compound node
  8442. if ( cy.hasCompoundNodes() )
  8443. {
  8444. r.updateAllCompounds(elements);
  8445. }
  8446. // } console.timeEnd('updatecompounds')
  8447. }
  8448. var elesInDragLayer;
  8449. var elesNotInDragLayer;
  8450. var element;
  8451. // console.time('drawing'); for( var looper = 0; looper <= looperMax; looper++ ){
  8452. if (data.canvasNeedsRedraw[NODE] || drawAll) {
  8453. // console.log("redrawing node layer", data.canvasRedrawReason[NODE]);
  8454. if( !elesInDragLayer || !elesNotInDragLayer ){
  8455. elesInDragLayer = [];
  8456. elesNotInDragLayer = [];
  8457. for (var index = 0; index < elements.length; index++) {
  8458. element = elements[index];
  8459. if ( element._private.rscratch.inDragLayer ) {
  8460. elesInDragLayer.push( element );
  8461. } else {
  8462. elesNotInDragLayer.push( element );
  8463. }
  8464. }
  8465. }
  8466. var context = forcedContext || data.canvases[NODE].getContext("2d");
  8467. context.setTransform(1, 0, 0, 1, 0, 0);
  8468. context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  8469. if( !drawAll ){
  8470. context.translate(effectivePan.x, effectivePan.y);
  8471. context.scale(effectiveZoom, effectiveZoom);
  8472. }
  8473. if( forcedPan ){
  8474. context.translate(forcedPan.x, forcedPan.y);
  8475. }
  8476. if( forcedZoom ){
  8477. context.scale(forcedZoom, forcedZoom);
  8478. }
  8479. for (var index = 0; index < elesNotInDragLayer.length; index++) {
  8480. element = elesNotInDragLayer[index];
  8481. if (element._private.group == "nodes") {
  8482. r.drawNode(context, element);
  8483. } else if (element._private.group == "edges") {
  8484. r.drawEdge(context, element);
  8485. }
  8486. }
  8487. for (var index = 0; index < elesNotInDragLayer.length; index++) {
  8488. element = elesNotInDragLayer[index];
  8489. if (element._private.group == "nodes") {
  8490. r.drawNodeText(context, element);
  8491. } else if (element._private.group == "edges") {
  8492. r.drawEdgeText(context, element);
  8493. }
  8494. // draw the overlay
  8495. if (element._private.group == "nodes") {
  8496. r.drawNode(context, element, true);
  8497. } else if (element._private.group == "edges") {
  8498. r.drawEdge(context, element, true);
  8499. }
  8500. }
  8501. if( !drawAll ){
  8502. data.canvasNeedsRedraw[NODE] = false; data.canvasRedrawReason[NODE] = [];
  8503. }
  8504. }
  8505. if (data.canvasNeedsRedraw[DRAG] || drawAll) {
  8506. // console.log("redrawing drag layer", data.canvasRedrawReason[DRAG]);
  8507. if( !elesInDragLayer || !elesNotInDragLayer ){
  8508. elesInDragLayer = [];
  8509. elesNotInDragLayer = [];
  8510. for (var index = 0; index < elements.length; index++) {
  8511. element = elements[index];
  8512. if ( element._private.rscratch.inDragLayer ) {
  8513. elesInDragLayer.push( element );
  8514. } else {
  8515. elesNotInDragLayer.push( element );
  8516. }
  8517. }
  8518. }
  8519. var context = forcedContext || data.canvases[DRAG].getContext("2d");
  8520. if( !drawAll ){
  8521. context.setTransform(1, 0, 0, 1, 0, 0);
  8522. context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  8523. context.translate(effectivePan.x, effectivePan.y);
  8524. context.scale(effectiveZoom, effectiveZoom);
  8525. }
  8526. if( forcedPan ){
  8527. context.translate(forcedPan.x, forcedPan.y);
  8528. }
  8529. if( forcedZoom ){
  8530. context.scale(forcedZoom, forcedZoom);
  8531. }
  8532. var element;
  8533. for (var index = 0; index < elesInDragLayer.length; index++) {
  8534. element = elesInDragLayer[index];
  8535. if (element._private.group == "nodes") {
  8536. r.drawNode(context, element);
  8537. } else if (element._private.group == "edges") {
  8538. r.drawEdge(context, element);
  8539. }
  8540. }
  8541. for (var index = 0; index < elesInDragLayer.length; index++) {
  8542. element = elesInDragLayer[index];
  8543. if (element._private.group == "nodes") {
  8544. r.drawNodeText(context, element);
  8545. } else if (element._private.group == "edges") {
  8546. r.drawEdgeText(context, element);
  8547. }
  8548. // draw the overlay
  8549. if (element._private.group == "nodes") {
  8550. r.drawNode(context, element, true);
  8551. } else if (element._private.group == "edges") {
  8552. r.drawEdge(context, element, true);
  8553. }
  8554. }
  8555. if( !drawAll ){
  8556. data.canvasNeedsRedraw[DRAG] = false; data.canvasRedrawReason[DRAG] = [];
  8557. }
  8558. }
  8559. if (data.canvasNeedsRedraw[SELECT_BOX]) {
  8560. // console.log("redrawing selection box", data.canvasRedrawReason[SELECT_BOX]);
  8561. var context = forcedContext || data.canvases[SELECT_BOX].getContext("2d");
  8562. if( !drawAll ){
  8563. context.setTransform(1, 0, 0, 1, 0, 0);
  8564. context.clearRect(0, 0, context.canvas.width, context.canvas.height);
  8565. context.translate(effectivePan.x, effectivePan.y);
  8566. context.scale(effectiveZoom, effectiveZoom);
  8567. }
  8568. if( forcedPan ){
  8569. context.translate(forcedPan.x, forcedPan.y);
  8570. }
  8571. if( forcedZoom ){
  8572. context.scale(forcedZoom, forcedZoom);
  8573. }
  8574. var coreStyle = cy.style()._private.coreStyle;
  8575. if (data.select[4] == 1) {
  8576. var zoom = data.cy.zoom();
  8577. var borderWidth = coreStyle["selection-box-border-width"].value / zoom;
  8578. context.lineWidth = borderWidth;
  8579. context.fillStyle = "rgba("
  8580. + coreStyle["selection-box-color"].value[0] + ","
  8581. + coreStyle["selection-box-color"].value[1] + ","
  8582. + coreStyle["selection-box-color"].value[2] + ","
  8583. + coreStyle["selection-box-opacity"].value + ")";
  8584. context.fillRect(
  8585. data.select[0],
  8586. data.select[1],
  8587. data.select[2] - data.select[0],
  8588. data.select[3] - data.select[1]);
  8589. if (borderWidth > 0) {
  8590. context.strokeStyle = "rgba("
  8591. + coreStyle["selection-box-border-color"].value[0] + ","
  8592. + coreStyle["selection-box-border-color"].value[1] + ","
  8593. + coreStyle["selection-box-border-color"].value[2] + ","
  8594. + coreStyle["selection-box-opacity"].value + ")";
  8595. context.strokeRect(
  8596. data.select[0],
  8597. data.select[1],
  8598. data.select[2] - data.select[0],
  8599. data.select[3] - data.select[1]);
  8600. }
  8601. }
  8602. if( data.bgActivePosistion ){
  8603. var zoom = data.cy.zoom();
  8604. var pos = data.bgActivePosistion;
  8605. context.fillStyle = "rgba("
  8606. + coreStyle["active-bg-color"].value[0] + ","
  8607. + coreStyle["active-bg-color"].value[1] + ","
  8608. + coreStyle["active-bg-color"].value[2] + ","
  8609. + coreStyle["active-bg-opacity"].value + ")";
  8610. context.beginPath();
  8611. context.arc(pos.x, pos.y, coreStyle["active-bg-size"].pxValue / zoom, 0, 2 * Math.PI);
  8612. context.fill();
  8613. }
  8614. if( !drawAll ){
  8615. data.canvasNeedsRedraw[SELECT_BOX] = false; data.canvasRedrawReason[SELECT_BOX] = [];
  8616. }
  8617. }
  8618. if( r.options.showOverlay ){
  8619. var context = data.canvases[OVERLAY].getContext("2d");
  8620. context.lineJoin = 'round';
  8621. context.font = '14px helvetica';
  8622. context.strokeStyle = '#fff';
  8623. context.lineWidth = '4';
  8624. context.fillStyle = '#666';
  8625. context.textAlign = 'right';
  8626. var text = 'cytoscape.js';
  8627. var w = context.canvas.width;
  8628. var h = context.canvas.height;
  8629. var p = 4;
  8630. var tw = context.measureText(text).width;
  8631. var th = 14;
  8632. context.clearRect(0, 0, w, h);
  8633. context.strokeText(text, w - p, h - p);
  8634. context.fillText(text, w - p, h - p);
  8635. data.overlayDrawn = true;
  8636. }
  8637. // } console.timeEnd('drawing')
  8638. var endTime = +new Date;
  8639. if( r.averageRedrawTime === undefined ){
  8640. r.averageRedrawTime = endTime - startTime;
  8641. }
  8642. // use a weighted average with a bias from the previous average so we don't spike so easily
  8643. r.averageRedrawTime = r.averageRedrawTime/2 + (endTime - startTime)/2;
  8644. //console.log('actual: %i, average: %i', endTime - startTime, this.averageRedrawTime);
  8645. // end on thread ready
  8646. }, 0);
  8647. };
  8648. var imageCache = {};
  8649. // Discard after 5 min. of disuse
  8650. var IMAGE_KEEP_TIME = 30 * 300; // 300frames@30fps, or. 5min
  8651. CanvasRenderer.prototype.getCachedImage = function(url, onLoadRedraw) {
  8652. if (imageCache[url] && imageCache[url].image) {
  8653. // Reset image discard timer
  8654. imageCache[url].keepTime = IMAGE_KEEP_TIME;
  8655. return imageCache[url].image;
  8656. }
  8657. var imageContainer = imageCache[url];
  8658. if (imageContainer == undefined) {
  8659. imageCache[url] = new Object();
  8660. imageCache[url].image = new Image();
  8661. imageCache[url].image.onload = onLoadRedraw;
  8662. imageCache[url].image.src = url;
  8663. // Initialize image discard timer
  8664. imageCache[url].keepTime = IMAGE_KEEP_TIME;
  8665. imageContainer = imageCache[url];
  8666. }
  8667. return imageContainer.image;
  8668. }
  8669. // Attempt to replace the image object with a canvas buffer to solve zooming problem
  8670. CanvasRenderer.prototype.swapCachedImage = function(url) {
  8671. if (imageCache[url]) {
  8672. if (imageCache[url].image
  8673. && imageCache[url].image.complete) {
  8674. var image = imageCache[url].image;
  8675. var buffer = document.createElement("canvas");
  8676. buffer.width = image.width;
  8677. buffer.height = image.height;
  8678. buffer.getContext("2d").drawImage(image,
  8679. 0, 0
  8680. );
  8681. imageCache[url].image = buffer;
  8682. imageCache[url].swappedWithCanvas = true;
  8683. return buffer;
  8684. } else {
  8685. return null;
  8686. }
  8687. } else {
  8688. return null;
  8689. }
  8690. }
  8691. CanvasRenderer.prototype.updateImageCaches = function() {
  8692. for (var url in imageCache) {
  8693. if (imageCache[url].keepTime <= 0) {
  8694. if (imageCache[url].image != undefined) {
  8695. imageCache[url].image.src = undefined;
  8696. imageCache[url].image = undefined;
  8697. }
  8698. imageCache[url] = undefined;
  8699. } else {
  8700. imageCache[url] -= 1;
  8701. }
  8702. }
  8703. }
  8704. CanvasRenderer.prototype.drawImage = function(context, x, y, widthScale, heightScale, rotationCW, image) {
  8705. image.widthScale = 0.5;
  8706. image.heightScale = 0.5;
  8707. image.rotate = rotationCW;
  8708. var finalWidth; var finalHeight;
  8709. canvas.drawImage(image, x, y);
  8710. }
  8711. // Draw edge
  8712. CanvasRenderer.prototype.drawEdge = function(context, edge, drawOverlayInstead) {
  8713. if( this.hideEdgesOnViewport && (this.dragData.didDrag || this.pinching || this.hoverData.dragging || this.data.wheel || this.swipePanning) ){ return; } // save cycles on pinching
  8714. var startNode, endNode;
  8715. startNode = edge.source()[0];
  8716. endNode = edge.target()[0];
  8717. if (edge._private.style["visibility"].value != "visible"
  8718. || startNode._private.style["visibility"].value != "visible"
  8719. || endNode._private.style["visibility"].value != "visible") {
  8720. return;
  8721. }
  8722. var overlayPadding = edge._private.style["overlay-padding"].value;
  8723. var overlayOpacity = edge._private.style["overlay-opacity"].value;
  8724. var overlayColor = edge._private.style["overlay-color"].value;
  8725. // Edge color & opacity
  8726. if( drawOverlayInstead ){
  8727. context.strokeStyle = "rgba( " + overlayColor[0] + ", " + overlayColor[1] + ", " + overlayColor[2] + ", " + overlayOpacity + " )";
  8728. context.lineCap = "round";
  8729. if( edge._private.rscratch.edgeType == "self"){
  8730. context.lineCap = "butt";
  8731. }
  8732. } else {
  8733. context.strokeStyle = "rgba("
  8734. + edge._private.style["line-color"].value[0] + ","
  8735. + edge._private.style["line-color"].value[1] + ","
  8736. + edge._private.style["line-color"].value[2] + ","
  8737. + edge._private.style.opacity.value + ")";
  8738. }
  8739. // Edge line width
  8740. if (edge._private.style["width"].value <= 0) {
  8741. return;
  8742. }
  8743. var edgeWidth = edge._private.style["width"].value + (drawOverlayInstead ? 2 * overlayPadding : 0);
  8744. var lineStyle = drawOverlayInstead ? "solid" : edge._private.style["line-style"].value;
  8745. context.lineWidth = edgeWidth;
  8746. this.findEndpoints(edge);
  8747. if (edge._private.rscratch.edgeType == "self") {
  8748. var details = edge._private.rscratch;
  8749. this.drawStyledEdge(edge, context, [details.startX, details.startY, details.cp2ax,
  8750. details.cp2ay, details.selfEdgeMidX, details.selfEdgeMidY],
  8751. lineStyle,
  8752. edgeWidth);
  8753. this.drawStyledEdge(edge, context, [details.selfEdgeMidX, details.selfEdgeMidY,
  8754. details.cp2cx, details.cp2cy, details.endX, details.endY],
  8755. lineStyle,
  8756. edgeWidth);
  8757. } else if (edge._private.rscratch.edgeType == "straight") {
  8758. var nodeDirectionX = endNode._private.position.x - startNode._private.position.x;
  8759. var nodeDirectionY = endNode._private.position.y - startNode._private.position.y;
  8760. var edgeDirectionX = edge._private.rscratch.endX - edge._private.rscratch.startX;
  8761. var edgeDirectionY = edge._private.rscratch.endY - edge._private.rscratch.startY;
  8762. if (nodeDirectionX * edgeDirectionX
  8763. + nodeDirectionY * edgeDirectionY < 0) {
  8764. edge._private.rscratch.straightEdgeTooShort = true;
  8765. } else {
  8766. var details = edge._private.rscratch;
  8767. this.drawStyledEdge(edge, context, [details.startX, details.startY,
  8768. details.endX, details.endY],
  8769. lineStyle,
  8770. edgeWidth);
  8771. edge._private.rscratch.straightEdgeTooShort = false;
  8772. }
  8773. } else {
  8774. var details = edge._private.rscratch;
  8775. this.drawStyledEdge(edge, context, [details.startX, details.startY,
  8776. details.cp2x, details.cp2y, details.endX, details.endY],
  8777. lineStyle,
  8778. edgeWidth);
  8779. }
  8780. if (edge._private.rscratch.noArrowPlacement !== true
  8781. && edge._private.rscratch.startX !== undefined) {
  8782. this.drawArrowheads(context, edge, drawOverlayInstead);
  8783. }
  8784. }
  8785. var _genPoints = function(pt, spacing, even) {
  8786. var approxLen = Math.sqrt(Math.pow(pt[4] - pt[0], 2) + Math.pow(pt[5] - pt[1], 2));
  8787. approxLen += Math.sqrt(Math.pow((pt[4] + pt[0]) / 2 - pt[2], 2) + Math.pow((pt[5] + pt[1]) / 2 - pt[3], 2));
  8788. var pts = Math.ceil(approxLen / spacing); var inc = approxLen / spacing;
  8789. var pz;
  8790. if (pts > 0) {
  8791. pz = new Array(pts * 2);
  8792. } else {
  8793. return null;
  8794. }
  8795. for (var i = 0; i < pts; i++) {
  8796. var cur = i / pts;
  8797. pz[i * 2] = pt[0] * (1 - cur) * (1 - cur) + 2 * (pt[2]) * (1 - cur) * cur + pt[4] * (cur) * (cur);
  8798. pz[i * 2 + 1] = pt[1] * (1 - cur) * (1 - cur) + 2 * (pt[3]) * (1 - cur) * cur + pt[5] * (cur) * (cur);
  8799. }
  8800. return pz;
  8801. }
  8802. var _genStraightLinePoints = function(pt, spacing, even) {
  8803. var approxLen = Math.sqrt(Math.pow(pt[2] - pt[0], 2) + Math.pow(pt[3] - pt[1], 2));
  8804. var pts = Math.ceil(approxLen / spacing);
  8805. var pz;
  8806. if (pts > 0) {
  8807. pz = new Array(pts * 2);
  8808. } else {
  8809. return null;
  8810. }
  8811. var lineOffset = [pt[2] - pt[0], pt[3] - pt[1]];
  8812. for (var i = 0; i < pts; i++) {
  8813. var cur = i / pts;
  8814. pz[i * 2] = lineOffset[0] * cur + pt[0];
  8815. pz[i * 2 + 1] = lineOffset[1] * cur + pt[1];
  8816. }
  8817. return pz;
  8818. }
  8819. var _genEvenOddpts = function(pt, evenspac, oddspac) {
  8820. pt1 = _genpts(pt, evenspac);
  8821. pt2 = _genpts(pt, oddspac);
  8822. }
  8823. CanvasRenderer.prototype.createBuffer = function(w, h) {
  8824. var buffer = document.createElement("canvas");
  8825. buffer.width = w;
  8826. buffer.height = h;
  8827. return [buffer, buffer.getContext("2d")];
  8828. }
  8829. /*
  8830. CanvasRenderer.prototype.
  8831. CanvasRenderer.prototype.drawStraightEdge = function(context, x1, y1, x2, y2, type, width) {
  8832. if (type == "solid") {
  8833. context.beginPath();
  8834. context.moveTo(
  8835. edge._private.rscratch.startX,
  8836. edge._private.rscratch.startY);
  8837. context.stroke();
  8838. } else if (type == "dotted") {
  8839. var pt = _genStraightLinePoints([x1, y1, x2, y2], 10, false);
  8840. } else if (type == "dashed") {
  8841. var pt = _genStraightLinePoints([x1, y1, x2, y2], 10, false);
  8842. }
  8843. }
  8844. */
  8845. CanvasRenderer.prototype.drawStyledEdge = function(
  8846. edge, context, pts, type, width) {
  8847. // 3 points given -> assume Bezier
  8848. // 2 -> assume straight
  8849. var cy = this.data.cy;
  8850. var zoom = cy.zoom();
  8851. // Adjusted edge width for dotted
  8852. // width = Math.max(width * 1.6, 3.4) * zoom;
  8853. // console.log("w", width);
  8854. // from http://en.wikipedia.org/wiki/Bézier_curve#Quadratic_curves
  8855. function qbezierAt(p0, p1, p2, t){
  8856. return (1 - t)*(1 - t)*p0 + 2*(1 - t)*t*p1 + t*t*p2;
  8857. }
  8858. if( edge._private.rstyle.bezierPts === undefined ){
  8859. edge._private.rstyle.bezierPts = [];
  8860. }
  8861. var nBpts = edge._private.rstyle.bezierPts.length;
  8862. if( edge.isLoop() ){
  8863. if( nBpts >= 12 ){
  8864. edge._private.rstyle.bezierPts = [];
  8865. } else {
  8866. // append to current array
  8867. }
  8868. } else {
  8869. edge._private.rstyle.bezierPts = [];
  8870. }
  8871. var bpts = edge._private.rstyle.bezierPts;
  8872. if( pts.length === 6 ){
  8873. bpts.push({
  8874. x: qbezierAt( pts[0], pts[2], pts[4], 0.05 ),
  8875. y: qbezierAt( pts[1], pts[3], pts[5], 0.05 )
  8876. });
  8877. bpts.push({
  8878. x: qbezierAt( pts[0], pts[2], pts[4], 0.25 ),
  8879. y: qbezierAt( pts[1], pts[3], pts[5], 0.25 )
  8880. });
  8881. bpts.push({
  8882. x: qbezierAt( pts[0], pts[2], pts[4], 0.35 ),
  8883. y: qbezierAt( pts[1], pts[3], pts[5], 0.35 )
  8884. });
  8885. bpts.push({
  8886. x: qbezierAt( pts[0], pts[2], pts[4], 0.65 ),
  8887. y: qbezierAt( pts[1], pts[3], pts[5], 0.65 )
  8888. });
  8889. bpts.push({
  8890. x: qbezierAt( pts[0], pts[2], pts[4], 0.75 ),
  8891. y: qbezierAt( pts[1], pts[3], pts[5], 0.75 )
  8892. });
  8893. bpts.push({
  8894. x: qbezierAt( pts[0], pts[2], pts[4], 0.95 ),
  8895. y: qbezierAt( pts[1], pts[3], pts[5], 0.95 )
  8896. });
  8897. }
  8898. if (type == "solid") {
  8899. context.beginPath();
  8900. context.moveTo(pts[0], pts[1]);
  8901. if (pts.length == 3 * 2) {
  8902. context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
  8903. } else {
  8904. context.lineTo(pts[2], pts[3]);
  8905. }
  8906. // context.closePath();
  8907. context.stroke();
  8908. } else if (type == "dotted") {
  8909. var pt;
  8910. if (pts.length == 3 * 2) {
  8911. pt = _genPoints(pts, 16, true);
  8912. } else {
  8913. pt = _genStraightLinePoints(pts, 16, true);
  8914. }
  8915. if (!pt) { return; }
  8916. var dotRadius = Math.max(width * 1.6, 3.4) * zoom;
  8917. var bufW = dotRadius * 2, bufH = dotRadius * 2;
  8918. bufW = Math.max(bufW, 1);
  8919. bufH = Math.max(bufH, 1);
  8920. var buffer = this.createBuffer(bufW, bufH);
  8921. var context2 = buffer[1];
  8922. // console.log(buffer);
  8923. // console.log(bufW, bufH);
  8924. // Draw on buffer
  8925. context2.setTransform(1, 0, 0, 1, 0, 0);
  8926. context2.clearRect(0, 0, bufW, bufH);
  8927. context2.fillStyle = context.strokeStyle;
  8928. context2.beginPath();
  8929. context2.arc(bufW/2, bufH/2, dotRadius * 0.5, 0, Math.PI * 2, false);
  8930. context2.fill();
  8931. // Now use buffer
  8932. context.beginPath();
  8933. //context.save();
  8934. for (var i=0; i<pt.length/2; i++) {
  8935. // context.beginPath();
  8936. // context.arc(pt[i*2], pt[i*2+1], width * 0.5, 0, Math.PI * 2, false);
  8937. // context.fill();
  8938. context.drawImage(
  8939. buffer[0],
  8940. pt[i*2] - bufW/2 / zoom,
  8941. pt[i*2+1] - bufH/2 / zoom,
  8942. bufW / zoom,
  8943. bufH / zoom);
  8944. }
  8945. //context.restore();
  8946. } else if (type == "dashed") {
  8947. var pt;
  8948. if (pts.length == 3 * 2) {
  8949. pt = _genPoints(pts, 14, true);
  8950. } else {
  8951. pt = _genStraightLinePoints(pts, 14, true);
  8952. }
  8953. if (!pt) { return; }
  8954. // var dashSize = Math.max(width * 1.6, 3.4);
  8955. // dashSize = Math.min(dashSize)
  8956. //var bufW = width * 2 * zoom, bufH = width * 2.5 * zoom;
  8957. var bufW = width * 2 * zoom
  8958. var bufH = 7.8 * zoom;
  8959. bufW = Math.max(bufW, 1);
  8960. bufH = Math.max(bufH, 1);
  8961. var buffer = this.createBuffer(bufW, bufH);
  8962. var context2 = buffer[1];
  8963. // Draw on buffer
  8964. context2.setTransform(1, 0, 0, 1, 0, 0);
  8965. context2.clearRect(0, 0, bufW, bufH);
  8966. if (context.strokeStyle) {
  8967. context2.strokeStyle = context.strokeStyle;
  8968. }
  8969. context2.lineWidth = width * cy.zoom();
  8970. // context2.fillStyle = context.strokeStyle;
  8971. context2.beginPath();
  8972. context2.moveTo(bufW / 2, bufH * 0.2);
  8973. context2.lineTo(bufW / 2, bufH * 0.8);
  8974. // context2.arc(bufH, dotRadius, dotRadius * 0.5, 0, Math.PI * 2, false);
  8975. // context2.fill();
  8976. context2.stroke();
  8977. //context.save();
  8978. // document.body.appendChild(buffer[0]);
  8979. var quadraticBezierVaryingTangent = false;
  8980. var rotateVector, angle;
  8981. // Straight line; constant tangent angle
  8982. if (pts.length == 2 * 2) {
  8983. rotateVector = [pts[2] - pts[0], pts[3] - pt[1]];
  8984. angle = Math.acos((rotateVector[0] * 0 + rotateVector[1] * -1) / Math.sqrt(rotateVector[0] * rotateVector[0]
  8985. + rotateVector[1] * rotateVector[1]));
  8986. if (rotateVector[0] < 0) {
  8987. angle = -angle + 2 * Math.PI;
  8988. }
  8989. } else if (pts.length == 3 * 2) {
  8990. quadraticBezierVaryingTangent = true;
  8991. }
  8992. for (var i=0; i<pt.length/2; i++) {
  8993. var p = i / (Math.max(pt.length/2 - 1, 1));
  8994. // Quadratic bezier; varying tangent
  8995. // So, use derivative of quadratic Bezier function to find tangents
  8996. if (quadraticBezierVaryingTangent) {
  8997. rotateVector = [2 * (1-p) * (pts[2] - pts[0])
  8998. + 2 * p * (pts[4] - pts[2]),
  8999. 2 * (1-p) * (pts[3] - pts[1])
  9000. + 2 * p * (pts[5] - pts[3])];
  9001. angle = Math.acos((rotateVector[0] * 0 + rotateVector[1] * -1) / Math.sqrt(rotateVector[0] * rotateVector[0]
  9002. + rotateVector[1] * rotateVector[1]));
  9003. if (rotateVector[0] < 0) {
  9004. angle = -angle + 2 * Math.PI;
  9005. }
  9006. }
  9007. context.translate(pt[i*2], pt[i*2+1]);
  9008. context.rotate(angle);
  9009. context.translate(-bufW/2 / zoom, -bufH/2 / zoom);
  9010. context.drawImage(
  9011. buffer[0],
  9012. 0,
  9013. 0,
  9014. bufW / zoom,
  9015. bufH / zoom);
  9016. context.translate(bufW/2 / zoom, bufH/2 / zoom);
  9017. context.rotate(-angle);
  9018. context.translate(-pt[i*2], -pt[i*2+1]);
  9019. }
  9020. //context.restore();
  9021. } else {
  9022. this.drawStyledEdge(edge, context, pts, "solid", width);
  9023. }
  9024. };
  9025. // Draw edge text
  9026. CanvasRenderer.prototype.drawEdgeText = function(context, edge) {
  9027. if( this.hideEdgesOnViewport && (this.dragData.didDrag || this.pinching || this.hoverData.dragging || this.data.wheel || this.swipePanning) ){ return; } // save cycles on pinching
  9028. if (edge._private.style["visibility"].value != "visible") {
  9029. return;
  9030. }
  9031. var computedSize = edge._private.style["font-size"].pxValue * edge.cy().zoom();
  9032. var minSize = edge._private.style["min-zoomed-font-size"].pxValue;
  9033. if( computedSize < minSize ){
  9034. return;
  9035. }
  9036. // Calculate text draw position
  9037. context.textAlign = "center";
  9038. context.textBaseline = "middle";
  9039. var textX, textY;
  9040. var edgeCenterX, edgeCenterY;
  9041. if (edge._private.rscratch.edgeType == "self") {
  9042. edgeCenterX = edge._private.rscratch.selfEdgeMidX;
  9043. edgeCenterY = edge._private.rscratch.selfEdgeMidY;
  9044. } else if (edge._private.rscratch.edgeType == "straight") {
  9045. edgeCenterX = (edge._private.rscratch.startX
  9046. + edge._private.rscratch.endX) / 2;
  9047. edgeCenterY = (edge._private.rscratch.startY
  9048. + edge._private.rscratch.endY) / 2;
  9049. } else if (edge._private.rscratch.edgeType == "bezier") {
  9050. edgeCenterX = 0.25 * edge._private.rscratch.startX
  9051. + 2 * 0.5 * 0.5 * edge._private.rscratch.cp2x
  9052. + (0.5 * 0.5) * edge._private.rscratch.endX;
  9053. edgeCenterY = Math.pow(1 - 0.5, 2) * edge._private.rscratch.startY
  9054. + 2 * (1 - 0.5) * 0.5 * edge._private.rscratch.cp2y
  9055. + (0.5 * 0.5) * edge._private.rscratch.endY;
  9056. }
  9057. textX = edgeCenterX;
  9058. textY = edgeCenterY;
  9059. // add center point to style so bounding box calculations can use it
  9060. var rstyle = edge._private.rstyle;
  9061. rstyle.labelX = textX;
  9062. rstyle.labelY = textY;
  9063. this.drawText(context, edge, textX, textY);
  9064. };
  9065. // Draw node
  9066. CanvasRenderer.prototype.drawNode = function(context, node, drawOverlayInstead) {
  9067. var nodeWidth, nodeHeight;
  9068. if ( node._private.style["visibility"].value != "visible" ) {
  9069. return;
  9070. }
  9071. var parentOpacity = 1;
  9072. var parents = node.parents();
  9073. for( var i = 0; i < parents.length; i++ ){
  9074. var parent = parents[i];
  9075. var opacity = parent._private.style.opacity.value;
  9076. parentOpacity = opacity * parentOpacity;
  9077. if( opacity === 0 ){
  9078. return;
  9079. }
  9080. }
  9081. nodeWidth = this.getNodeWidth(node);
  9082. nodeHeight = this.getNodeHeight(node);
  9083. context.lineWidth = node._private.style["border-width"].pxValue;
  9084. if( drawOverlayInstead === undefined || !drawOverlayInstead ){
  9085. // Node color & opacity
  9086. context.fillStyle = "rgba("
  9087. + node._private.style["background-color"].value[0] + ","
  9088. + node._private.style["background-color"].value[1] + ","
  9089. + node._private.style["background-color"].value[2] + ","
  9090. + (node._private.style["background-opacity"].value
  9091. * node._private.style["opacity"].value * parentOpacity) + ")";
  9092. // Node border color & opacity
  9093. context.strokeStyle = "rgba("
  9094. + node._private.style["border-color"].value[0] + ","
  9095. + node._private.style["border-color"].value[1] + ","
  9096. + node._private.style["border-color"].value[2] + ","
  9097. + (node._private.style["border-opacity"].value * node._private.style["opacity"].value * parentOpacity) + ")";
  9098. {
  9099. //var image = this.getCachedImage("url");
  9100. var url = node._private.style["background-image"].value[2] ||
  9101. node._private.style["background-image"].value[1];
  9102. if (url != undefined) {
  9103. var r = this;
  9104. var image = this.getCachedImage(url,
  9105. function() {
  9106. // console.log(e);
  9107. r.data.canvasNeedsRedraw[NODE] = true;
  9108. r.data.canvasRedrawReason[NODE].push("image finished load");
  9109. r.data.canvasNeedsRedraw[DRAG] = true;
  9110. r.data.canvasRedrawReason[DRAG].push("image finished load");
  9111. // Replace Image object with Canvas to solve zooming too far
  9112. // into image graphical errors (Jan 10 2013)
  9113. r.swapCachedImage(url);
  9114. r.redraw();
  9115. }
  9116. );
  9117. if (image.complete == false) {
  9118. nodeShapes[r.getNodeShape(node)].drawPath(
  9119. context,
  9120. node._private.position.x,
  9121. node._private.position.y,
  9122. nodeWidth, nodeHeight);
  9123. //node._private.style["width"].value,
  9124. //node._private.style["height"].value);
  9125. context.stroke();
  9126. context.fillStyle = "#555555";
  9127. context.fill();
  9128. } else {
  9129. //context.clip
  9130. this.drawInscribedImage(context, image, node);
  9131. }
  9132. } else {
  9133. // Draw node
  9134. nodeShapes[this.getNodeShape(node)].draw(
  9135. context,
  9136. node._private.position.x,
  9137. node._private.position.y,
  9138. nodeWidth,
  9139. nodeHeight); //node._private.data.weight / 5.0
  9140. }
  9141. }
  9142. // Border width, draw border
  9143. if (node._private.style["border-width"].value > 0) {
  9144. context.stroke();
  9145. }
  9146. // draw the overlay
  9147. } else {
  9148. var overlayPadding = node._private.style["overlay-padding"].value;
  9149. var overlayOpacity = node._private.style["overlay-opacity"].value;
  9150. var overlayColor = node._private.style["overlay-color"].value;
  9151. if( overlayOpacity > 0 ){
  9152. context.fillStyle = "rgba( " + overlayColor[0] + ", " + overlayColor[1] + ", " + overlayColor[2] + ", " + overlayOpacity + " )";
  9153. nodeShapes[this.getNodeShape(node)].draw(
  9154. context,
  9155. node._private.position.x,
  9156. node._private.position.y,
  9157. nodeWidth + overlayPadding * 2,
  9158. nodeHeight + overlayPadding * 2
  9159. );
  9160. }
  9161. }
  9162. };
  9163. CanvasRenderer.prototype.drawInscribedImage = function(context, img, node) {
  9164. var r = this;
  9165. // console.log(this.data);
  9166. var zoom = this.data.cy._private.zoom;
  9167. var nodeX = node._private.position.x;
  9168. var nodeY = node._private.position.y;
  9169. //var nodeWidth = node._private.style["width"].value;
  9170. //var nodeHeight = node._private.style["height"].value;
  9171. var nodeWidth = this.getNodeWidth(node);
  9172. var nodeHeight = this.getNodeHeight(node);
  9173. context.save();
  9174. nodeShapes[r.getNodeShape(node)].drawPath(
  9175. context,
  9176. nodeX, nodeY,
  9177. nodeWidth, nodeHeight);
  9178. context.clip();
  9179. // context.setTransform(1, 0, 0, 1, 0, 0);
  9180. var imgDim = [img.width, img.height];
  9181. context.drawImage(img,
  9182. nodeX - imgDim[0] / 2,
  9183. nodeY - imgDim[1] / 2,
  9184. imgDim[0],
  9185. imgDim[1]);
  9186. context.restore();
  9187. if (node._private.style["border-width"].value > 0) {
  9188. context.stroke();
  9189. }
  9190. };
  9191. // Draw node text
  9192. CanvasRenderer.prototype.drawNodeText = function(context, node) {
  9193. if (node._private.style["visibility"].value != "visible") {
  9194. return;
  9195. }
  9196. var computedSize = node._private.style["font-size"].pxValue * node.cy().zoom();
  9197. var minSize = node._private.style["min-zoomed-font-size"].pxValue;
  9198. if( computedSize < minSize ){
  9199. return;
  9200. }
  9201. var textX, textY;
  9202. //var nodeWidth = node._private.style["width"].value;
  9203. //var nodeHeight = node._private.style["height"].value;
  9204. var nodeWidth = this.getNodeWidth(node);
  9205. var nodeHeight = this.getNodeHeight(node);
  9206. // Find text position
  9207. var textHalign = node._private.style["text-halign"].strValue;
  9208. if (textHalign == "left") {
  9209. // Align right boundary of text with left boundary of node
  9210. context.textAlign = "right";
  9211. textX = node._private.position.x - nodeWidth / 2;
  9212. } else if (textHalign == "right") {
  9213. // Align left boundary of text with right boundary of node
  9214. context.textAlign = "left";
  9215. textX = node._private.position.x + nodeWidth / 2;
  9216. } else if (textHalign == "center") {
  9217. context.textAlign = "center";
  9218. textX = node._private.position.x;
  9219. } else {
  9220. // Same as center
  9221. context.textAlign = "center";
  9222. textX = node._private.position.x;
  9223. }
  9224. var textValign = node._private.style["text-valign"].strValue;
  9225. if (textValign == "top") {
  9226. context.textBaseline = "bottom";
  9227. textY = node._private.position.y - nodeHeight / 2;
  9228. } else if (textValign == "bottom") {
  9229. context.textBaseline = "top";
  9230. textY = node._private.position.y + nodeHeight / 2;
  9231. } else if (textValign == "middle" || textValign == "center") {
  9232. context.textBaseline = "middle";
  9233. textY = node._private.position.y;
  9234. } else {
  9235. // same as center
  9236. context.textBaseline = "middle";
  9237. textY = node._private.position.y;
  9238. }
  9239. this.drawText(context, node, textX, textY);
  9240. };
  9241. // Draw text
  9242. CanvasRenderer.prototype.drawText = function(context, element, textX, textY) {
  9243. var parentOpacity = 1;
  9244. var parents = element.parents();
  9245. for( var i = 0; i < parents.length; i++ ){
  9246. var parent = parents[i];
  9247. var opacity = parent._private.style.opacity.value;
  9248. parentOpacity = opacity * parentOpacity;
  9249. if( opacity === 0 ){
  9250. return;
  9251. }
  9252. }
  9253. // Font style
  9254. var labelStyle = element._private.style["font-style"].strValue;
  9255. var labelSize = element._private.style["font-size"].value + "px";
  9256. var labelFamily = element._private.style["font-family"].strValue;
  9257. var labelVariant = element._private.style["font-variant"].strValue;
  9258. var labelWeight = element._private.style["font-weight"].strValue;
  9259. context.font = labelStyle + " " + labelWeight + " "
  9260. + labelSize + " " + labelFamily;
  9261. var text = String(element._private.style["content"].value);
  9262. var textTransform = element._private.style["text-transform"].value;
  9263. if (textTransform == "none") {
  9264. } else if (textTransform == "uppercase") {
  9265. text = text.toUpperCase();
  9266. } else if (textTransform == "lowercase") {
  9267. text = text.toLowerCase();
  9268. }
  9269. // Calculate text draw position based on text alignment
  9270. // so text outlines aren't jagged
  9271. context.lineJoin = 'round';
  9272. context.fillStyle = "rgba("
  9273. + element._private.style["color"].value[0] + ","
  9274. + element._private.style["color"].value[1] + ","
  9275. + element._private.style["color"].value[2] + ","
  9276. + (element._private.style["text-opacity"].value
  9277. * element._private.style["opacity"].value * parentOpacity) + ")";
  9278. context.strokeStyle = "rgba("
  9279. + element._private.style["text-outline-color"].value[0] + ","
  9280. + element._private.style["text-outline-color"].value[1] + ","
  9281. + element._private.style["text-outline-color"].value[2] + ","
  9282. + (element._private.style["text-opacity"].value
  9283. * element._private.style["opacity"].value * parentOpacity) + ")";
  9284. if (text != undefined) {
  9285. var lineWidth = 2 * element._private.style["text-outline-width"].value; // *2 b/c the stroke is drawn centred on the middle
  9286. if (lineWidth > 0) {
  9287. context.lineWidth = lineWidth;
  9288. context.strokeText(text, textX, textY);
  9289. }
  9290. // Thanks sysord@github for the isNaN checks!
  9291. if (isNaN(textX)) { textX = 0; }
  9292. if (isNaN(textY)) { textY = 0; }
  9293. context.fillText("" + text, textX, textY);
  9294. // record the text's width for use in bounding box calc
  9295. element._private.rstyle.labelWidth = context.measureText( text ).width;
  9296. }
  9297. };
  9298. CanvasRenderer.prototype.drawBackground = function(context, color1, color2,
  9299. startPosition, endPosition) {
  9300. }
  9301. // @O Edge calculation functions
  9302. {
  9303. // Find edge control points
  9304. CanvasRenderer.prototype.findEdgeControlPoints = function(edges) {
  9305. var hashTable = {}; var cy = this.data.cy;
  9306. var pairIds = [];
  9307. var pairId;
  9308. for (var i = 0; i < edges.length; i++) {
  9309. pairId = edges[i]._private.data.source > edges[i]._private.data.target ?
  9310. edges[i]._private.data.target + '-' + edges[i]._private.data.source :
  9311. edges[i]._private.data.source + '-' + edges[i]._private.data.target ;
  9312. if (hashTable[pairId] == undefined) {
  9313. hashTable[pairId] = [];
  9314. }
  9315. hashTable[pairId].push( edges[i] );
  9316. pairIds.push( pairId );
  9317. }
  9318. var src, tgt;
  9319. // Nested for loop is OK; total number of iterations for both loops = edgeCount
  9320. for (var p = 0; p < pairIds.length; p++) {
  9321. pairId = pairIds[p];
  9322. src = cy.getElementById( hashTable[pairId][0]._private.data.source );
  9323. tgt = cy.getElementById( hashTable[pairId][0]._private.data.target );
  9324. var midPointX = (src._private.position.x + tgt._private.position.x) / 2;
  9325. var midPointY = (src._private.position.y + tgt._private.position.y) / 2;
  9326. var displacementX, displacementY;
  9327. if (hashTable[pairId].length > 1) {
  9328. displacementX = tgt._private.position.y - src._private.position.y;
  9329. displacementY = src._private.position.x - tgt._private.position.x;
  9330. var displacementLength = Math.sqrt(displacementX * displacementX
  9331. + displacementY * displacementY);
  9332. displacementX /= displacementLength;
  9333. displacementY /= displacementLength;
  9334. }
  9335. var edge;
  9336. for (var i = 0; i < hashTable[pairId].length; i++) {
  9337. edge = hashTable[pairId][i];
  9338. var edgeIndex1 = edge._private.rscratch.lastEdgeIndex;
  9339. var edgeIndex2 = i;
  9340. var numEdges1 = edge._private.rscratch.lastNumEdges;
  9341. var numEdges2 = hashTable[pairId].length;
  9342. var srcX1 = edge._private.rscratch.lastSrcCtlPtX;
  9343. var srcX2 = src._private.position.x;
  9344. var srcY1 = edge._private.rscratch.lastSrcCtlPtY;
  9345. var srcY2 = src._private.position.y;
  9346. var srcW1 = edge._private.rscratch.lastSrcCtlPtW;
  9347. var srcW2 = src.outerWidth();
  9348. var srcH1 = edge._private.rscratch.lastSrcCtlPtH;
  9349. var srcH2 = src.outerHeight();
  9350. var tgtX1 = edge._private.rscratch.lastTgtCtlPtX;
  9351. var tgtX2 = tgt._private.position.x;
  9352. var tgtY1 = edge._private.rscratch.lastTgtCtlPtY;
  9353. var tgtY2 = tgt._private.position.y;
  9354. var tgtW1 = edge._private.rscratch.lastTgtCtlPtW;
  9355. var tgtW2 = tgt.outerWidth();
  9356. var tgtH1 = edge._private.rscratch.lastTgtCtlPtH;
  9357. var tgtH2 = tgt.outerHeight();
  9358. if( srcX1 === srcX2 && srcY1 === srcY2 && srcW1 === srcW2 && srcH1 === srcH2
  9359. && tgtX1 === tgtX2 && tgtY1 === tgtY2 && tgtW1 === tgtW2 && tgtH1 === tgtH2
  9360. && edgeIndex1 === edgeIndex2 && numEdges1 === numEdges2 ){
  9361. // console.log('edge ctrl pt cache HIT')
  9362. continue; // then the control points haven't changed and we can skip calculating them
  9363. } else {
  9364. var rs = edge._private.rscratch;
  9365. rs.lastSrcCtlPtX = srcX2;
  9366. rs.lastSrcCtlPtY = srcY2;
  9367. rs.lastSrcCtlPtW = srcW2;
  9368. rs.lastSrcCtlPtH = srcH2;
  9369. rs.lastTgtCtlPtX = tgtX2;
  9370. rs.lastTgtCtlPtY = tgtY2;
  9371. rs.lastTgtCtlPtW = tgtW2;
  9372. rs.lastTgtCtlPtH = tgtH2;
  9373. rs.lastEdgeIndex = edgeIndex2;
  9374. rs.lastNumEdges = numEdges2;
  9375. // console.log('edge ctrl pt cache MISS')
  9376. }
  9377. // Self-edge
  9378. if (src._private.data.id == tgt._private.data.id) {
  9379. var stepSize = edge._private.style["control-point-step-size"].pxValue;
  9380. edge._private.rscratch.edgeType = "self";
  9381. // New -- fix for large nodes
  9382. edge._private.rscratch.cp2ax = src._private.position.x;
  9383. edge._private.rscratch.cp2ay = src._private.position.y
  9384. - (1 + Math.pow(this.getNodeHeight(src), 1.12) / 100) * stepSize * (i / 3 + 1);
  9385. edge._private.rscratch.cp2cx = src._private.position.x
  9386. - (1 + Math.pow(this.getNodeWidth(src), 1.12) / 100) * stepSize * (i / 3 + 1);
  9387. edge._private.rscratch.cp2cy = src._private.position.y;
  9388. edge._private.rscratch.selfEdgeMidX =
  9389. (edge._private.rscratch.cp2ax + edge._private.rscratch.cp2cx) / 2.0;
  9390. edge._private.rscratch.selfEdgeMidY =
  9391. (edge._private.rscratch.cp2ay + edge._private.rscratch.cp2cy) / 2.0;
  9392. // Straight edge
  9393. } else if (hashTable[pairId].length % 2 == 1
  9394. && i == Math.floor(hashTable[pairId].length / 2)) {
  9395. edge._private.rscratch.edgeType = "straight";
  9396. // Bezier edge
  9397. } else {
  9398. var stepSize = edge._private.style["control-point-step-size"].value;
  9399. var distanceFromMidpoint = (0.5 - hashTable[pairId].length / 2 + i) * stepSize;
  9400. edge._private.rscratch.edgeType = "bezier";
  9401. edge._private.rscratch.cp2x = midPointX
  9402. + displacementX * distanceFromMidpoint;
  9403. edge._private.rscratch.cp2y = midPointY
  9404. + displacementY * distanceFromMidpoint;
  9405. // console.log(edge, midPointX, displacementX, distanceFromMidpoint);
  9406. }
  9407. }
  9408. }
  9409. return hashTable;
  9410. }
  9411. CanvasRenderer.prototype.findEndpoints = function(edge) {
  9412. var intersect;
  9413. var source = edge.source()[0];
  9414. var target = edge.target()[0];
  9415. // var sourceRadius = Math.max(edge.source()[0]._private.style["width"].value,
  9416. // edge.source()[0]._private.style["height"].value);
  9417. var sourceRadius = Math.max(this.getNodeWidth(source),
  9418. this.getNodeHeight(source));
  9419. // var targetRadius = Math.max(edge.target()[0]._private.style["width"].value,
  9420. // edge.target()[0]._private.style["height"].value);
  9421. var targetRadius = Math.max(this.getNodeWidth(target),
  9422. this.getNodeHeight(target));
  9423. sourceRadius = 0;
  9424. targetRadius /= 2;
  9425. var start = [edge.source().position().x, edge.source().position().y];
  9426. var end = [edge.target().position().x, edge.target().position().y];
  9427. if (edge._private.rscratch.edgeType == "self") {
  9428. var cp = [edge._private.rscratch.cp2cx, edge._private.rscratch.cp2cy];
  9429. intersect = nodeShapes[this.getNodeShape(target)].intersectLine(
  9430. target._private.position.x,
  9431. target._private.position.y,
  9432. //target._private.style["width"].value,
  9433. //target._private.style["height"].value,
  9434. this.getNodeWidth(target),
  9435. this.getNodeHeight(target),
  9436. cp[0], //halfPointX,
  9437. cp[1], //halfPointY
  9438. target._private.style["border-width"].value / 2
  9439. );
  9440. var arrowEnd = this.shortenIntersection(intersect, cp,
  9441. arrowShapes[edge._private.style["target-arrow-shape"].value].spacing(edge));
  9442. var edgeEnd = this.shortenIntersection(intersect, cp,
  9443. arrowShapes[edge._private.style["target-arrow-shape"].value].gap(edge));
  9444. edge._private.rscratch.endX = edgeEnd[0];
  9445. edge._private.rscratch.endY = edgeEnd[1];
  9446. edge._private.rscratch.arrowEndX = arrowEnd[0];
  9447. edge._private.rscratch.arrowEndY = arrowEnd[1];
  9448. var cp = [edge._private.rscratch.cp2ax, edge._private.rscratch.cp2ay];
  9449. intersect = nodeShapes[this.getNodeShape(source)].intersectLine(
  9450. source._private.position.x,
  9451. source._private.position.y,
  9452. //source._private.style["width"].value,
  9453. //source._private.style["height"].value,
  9454. this.getNodeWidth(source),
  9455. this.getNodeHeight(source),
  9456. cp[0], //halfPointX,
  9457. cp[1], //halfPointY
  9458. source._private.style["border-width"].value / 2
  9459. );
  9460. var arrowStart = this.shortenIntersection(intersect, cp,
  9461. arrowShapes[edge._private.style["source-arrow-shape"].value].spacing(edge));
  9462. var edgeStart = this.shortenIntersection(intersect, cp,
  9463. arrowShapes[edge._private.style["source-arrow-shape"].value].gap(edge));
  9464. edge._private.rscratch.startX = edgeStart[0];
  9465. edge._private.rscratch.startY = edgeStart[1];
  9466. edge._private.rscratch.arrowStartX = arrowStart[0];
  9467. edge._private.rscratch.arrowStartY = arrowStart[1];
  9468. } else if (edge._private.rscratch.edgeType == "straight") {
  9469. intersect = nodeShapes[this.getNodeShape(target)].intersectLine(
  9470. target._private.position.x,
  9471. target._private.position.y,
  9472. //target._private.style["width"].value,
  9473. //target._private.style["height"].value,
  9474. this.getNodeWidth(target),
  9475. this.getNodeHeight(target),
  9476. source.position().x,
  9477. source.position().y,
  9478. target._private.style["border-width"].value / 2);
  9479. if (intersect.length == 0) {
  9480. edge._private.rscratch.noArrowPlacement = true;
  9481. // return;
  9482. } else {
  9483. edge._private.rscratch.noArrowPlacement = false;
  9484. }
  9485. var arrowEnd = this.shortenIntersection(intersect,
  9486. [source.position().x, source.position().y],
  9487. arrowShapes[edge._private.style["target-arrow-shape"].value].spacing(edge));
  9488. var edgeEnd = this.shortenIntersection(intersect,
  9489. [source.position().x, source.position().y],
  9490. arrowShapes[edge._private.style["target-arrow-shape"].value].gap(edge));
  9491. edge._private.rscratch.endX = edgeEnd[0];
  9492. edge._private.rscratch.endY = edgeEnd[1];
  9493. edge._private.rscratch.arrowEndX = arrowEnd[0];
  9494. edge._private.rscratch.arrowEndY = arrowEnd[1];
  9495. intersect = nodeShapes[this.getNodeShape(source)].intersectLine(
  9496. source._private.position.x,
  9497. source._private.position.y,
  9498. //source._private.style["width"].value,
  9499. //source._private.style["height"].value,
  9500. this.getNodeWidth(source),
  9501. this.getNodeHeight(source),
  9502. target.position().x,
  9503. target.position().y,
  9504. source._private.style["border-width"].value / 2);
  9505. if (intersect.length == 0) {
  9506. edge._private.rscratch.noArrowPlacement = true;
  9507. // return;
  9508. } else {
  9509. edge._private.rscratch.noArrowPlacement = false;
  9510. }
  9511. /*
  9512. console.log("1: "
  9513. + arrowShapes[edge._private.style["source-arrow-shape"].value],
  9514. edge._private.style["source-arrow-shape"].value);
  9515. */
  9516. var arrowStart = this.shortenIntersection(intersect,
  9517. [target.position().x, target.position().y],
  9518. arrowShapes[edge._private.style["source-arrow-shape"].value].spacing(edge));
  9519. var edgeStart = this.shortenIntersection(intersect,
  9520. [target.position().x, target.position().y],
  9521. arrowShapes[edge._private.style["source-arrow-shape"].value].gap(edge));
  9522. edge._private.rscratch.startX = edgeStart[0];
  9523. edge._private.rscratch.startY = edgeStart[1];
  9524. edge._private.rscratch.arrowStartX = arrowStart[0];
  9525. edge._private.rscratch.arrowStartY = arrowStart[1];
  9526. } else if (edge._private.rscratch.edgeType == "bezier") {
  9527. var cp = [edge._private.rscratch.cp2x, edge._private.rscratch.cp2y];
  9528. // Point at middle of Bezier
  9529. var halfPointX = start[0] * 0.25 + end[0] * 0.25 + cp[0] * 0.5;
  9530. var halfPointY = start[1] * 0.25 + end[1] * 0.25 + cp[1] * 0.5;
  9531. intersect = nodeShapes[
  9532. this.getNodeShape(target)].intersectLine(
  9533. target._private.position.x,
  9534. target._private.position.y,
  9535. //target._private.style["width"].value,
  9536. //target._private.style["height"].value,
  9537. this.getNodeWidth(target),
  9538. this.getNodeHeight(target),
  9539. cp[0], //halfPointX,
  9540. cp[1], //halfPointY
  9541. target._private.style["border-width"].value / 2
  9542. );
  9543. /*
  9544. console.log("2: "
  9545. + arrowShapes[edge._private.style["source-arrow-shape"].value],
  9546. edge._private.style["source-arrow-shape"].value);
  9547. */
  9548. var arrowEnd = this.shortenIntersection(intersect, cp,
  9549. arrowShapes[edge._private.style["target-arrow-shape"].value].spacing(edge));
  9550. var edgeEnd = this.shortenIntersection(intersect, cp,
  9551. arrowShapes[edge._private.style["target-arrow-shape"].value].gap(edge));
  9552. edge._private.rscratch.endX = edgeEnd[0];
  9553. edge._private.rscratch.endY = edgeEnd[1];
  9554. edge._private.rscratch.arrowEndX = arrowEnd[0];
  9555. edge._private.rscratch.arrowEndY = arrowEnd[1];
  9556. intersect = nodeShapes[
  9557. this.getNodeShape(source)].intersectLine(
  9558. source._private.position.x,
  9559. source._private.position.y,
  9560. //source._private.style["width"].value,
  9561. //source._private.style["height"].value,
  9562. this.getNodeWidth(source),
  9563. this.getNodeHeight(source),
  9564. cp[0], //halfPointX,
  9565. cp[1], //halfPointY
  9566. source._private.style["border-width"].value / 2
  9567. );
  9568. var arrowStart = this.shortenIntersection(intersect, cp,
  9569. arrowShapes[edge._private.style["source-arrow-shape"].value].spacing(edge));
  9570. var edgeStart = this.shortenIntersection(intersect, cp,
  9571. arrowShapes[edge._private.style["source-arrow-shape"].value].gap(edge));
  9572. edge._private.rscratch.startX = edgeStart[0];
  9573. edge._private.rscratch.startY = edgeStart[1];
  9574. edge._private.rscratch.arrowStartX = arrowStart[0];
  9575. edge._private.rscratch.arrowStartY = arrowStart[1];
  9576. } else if (edge._private.rscratch.isArcEdge) {
  9577. return;
  9578. }
  9579. }
  9580. }
  9581. // @O Graph traversal functions
  9582. {
  9583. // Find adjacent edges
  9584. CanvasRenderer.prototype.findEdges = function(nodeSet) {
  9585. var edges = this.getCachedEdges();
  9586. var hashTable = {};
  9587. var adjacentEdges = [];
  9588. for (var i = 0; i < nodeSet.length; i++) {
  9589. hashTable[nodeSet[i]._private.data.id] = nodeSet[i];
  9590. }
  9591. for (var i = 0; i < edges.length; i++) {
  9592. if (hashTable[edges[i]._private.data.source]
  9593. || hashTable[edges[i]._private.data.target]) {
  9594. adjacentEdges.push(edges[i]);
  9595. }
  9596. }
  9597. return adjacentEdges;
  9598. }
  9599. }
  9600. // @O Intersection functions
  9601. {
  9602. CanvasRenderer.prototype.intersectLineEllipse = function(
  9603. x, y, centerX, centerY, ellipseWradius, ellipseHradius) {
  9604. var dispX = centerX - x;
  9605. var dispY = centerY - y;
  9606. dispX /= ellipseWradius;
  9607. dispY /= ellipseHradius;
  9608. var len = Math.sqrt(dispX * dispX + dispY * dispY);
  9609. var newLength = len - 1;
  9610. if (newLength < 0) {
  9611. return [];
  9612. }
  9613. var lenProportion = newLength / len;
  9614. return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y];
  9615. }
  9616. CanvasRenderer.prototype.dotProduct = function(
  9617. vec1, vec2) {
  9618. if (vec1.length != 2 || vec2.length != 2) {
  9619. throw 'dot product: arguments are not vectors';
  9620. }
  9621. return (vec1[0] * vec2[0] + vec1[1] * vec2[1]);
  9622. }
  9623. // Returns intersections of increasing distance from line's start point
  9624. CanvasRenderer.prototype.intersectLineCircle = function(
  9625. x1, y1, x2, y2, centerX, centerY, radius) {
  9626. // Calculate d, direction vector of line
  9627. var d = [x2 - x1, y2 - y1]; // Direction vector of line
  9628. var s = [x1, y1]; // Start of line
  9629. var c = [centerX, centerY]; // Center of circle
  9630. var f = [x1 - centerX, y1 - centerY]
  9631. var a = d[0] * d[0] + d[1] * d[1];
  9632. var b = 2 * (f[0] * d[0] + f[1] * d[1]);
  9633. var c = (f[0] * f[0] + f[1] * f[1]) - radius * radius ;
  9634. /*
  9635. var a = this.dotProduct(d, d);
  9636. var b = 2 * this.dotProduct(s, d) - this.dotProduct(d, c);
  9637. var c = this.dotProduct(s, s) - 2 * this.dotProduct(s, c) + this.dotProduct(c, c) - radius * radius ;
  9638. */
  9639. var discriminant = b*b-4*a*c;
  9640. if (discriminant < 0) {
  9641. return [];
  9642. }
  9643. t1 = (-b + Math.sqrt(discriminant)) / (2 * a);
  9644. t2 = (-b - Math.sqrt(discriminant)) / (2 * a);
  9645. var tMin = Math.min(t1, t2);
  9646. var tMax = Math.max(t1, t2);
  9647. var inRangeParams = [];
  9648. if (tMin >= 0 && tMin <= 1) {
  9649. inRangeParams.push(tMin);
  9650. }
  9651. if (tMax >= 0 && tMax <= 1) {
  9652. inRangeParams.push(tMax);
  9653. }
  9654. if (inRangeParams.length == 0) {
  9655. return [];
  9656. }
  9657. var nearIntersectionX = inRangeParams[0] * d[0] + x1;
  9658. var nearIntersectionY = inRangeParams[0] * d[1] + y1;
  9659. if (inRangeParams.length > 1) {
  9660. if (inRangeParams[0] == inRangeParams[1]) {
  9661. return [nearIntersectionX, nearIntersectionY];
  9662. } else {
  9663. var farIntersectionX = inRangeParams[1] * d[0] + x1;
  9664. var farIntersectionY = inRangeParams[1] * d[1] + y1;
  9665. return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY];
  9666. }
  9667. } else {
  9668. return [nearIntersectionX, nearIntersectionY]
  9669. }
  9670. }
  9671. CanvasRenderer.prototype.findCircleNearPoint = function(centerX, centerY,
  9672. radius, farX, farY) {
  9673. var displacementX = farX - centerX;
  9674. var displacementY = farY - centerY;
  9675. var distance = Math.sqrt(displacementX * displacementX
  9676. + displacementY * displacementY);
  9677. var unitDisplacementX = displacementX / distance;
  9678. var unitDisplacementY = displacementY / distance;
  9679. return [centerX + unitDisplacementX * radius,
  9680. centerY + unitDisplacementY * radius];
  9681. }
  9682. CanvasRenderer.prototype.findMaxSqDistanceToOrigin = function(points) {
  9683. var maxSqDistance = 0.000001;
  9684. var sqDistance;
  9685. for (var i = 0; i < points.length / 2; i++) {
  9686. sqDistance = points[i * 2] * points[i * 2]
  9687. + points[i * 2 + 1] * points[i * 2 + 1];
  9688. if (sqDistance > maxSqDistance) {
  9689. maxSqDistance = sqDistance;
  9690. }
  9691. }
  9692. return maxSqDistance;
  9693. }
  9694. CanvasRenderer.prototype.finiteLinesIntersect = function(
  9695. x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines) {
  9696. var ua_t = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  9697. var ub_t = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
  9698. var u_b = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
  9699. if (u_b != 0) {
  9700. var ua = ua_t / u_b;
  9701. var ub = ub_t / u_b;
  9702. if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
  9703. return [x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)];
  9704. } else {
  9705. if (!infiniteLines) {
  9706. return [];
  9707. } else {
  9708. return [x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)];
  9709. }
  9710. }
  9711. } else {
  9712. if (ua_t == 0 || ub_t == 0) {
  9713. // Parallel, coincident lines. Check if overlap
  9714. // Check endpoint of second line
  9715. if ([x1, x2, x4].sort()[1] == x4) {
  9716. return [x4, y4];
  9717. }
  9718. // Check start point of second line
  9719. if ([x1, x2, x3].sort()[1] == x3) {
  9720. return [x3, y3];
  9721. }
  9722. // Endpoint of first line
  9723. if ([x3, x4, x2].sort()[1] == x2) {
  9724. return [x2, y2];
  9725. }
  9726. return [];
  9727. } else {
  9728. // Parallel, non-coincident
  9729. return [];
  9730. }
  9731. }
  9732. }
  9733. // (boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
  9734. // cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxTopLeftY + padding)) {
  9735. CanvasRenderer.prototype.boxIntersectEllipse = function(
  9736. x1, y1, x2, y2, padding, width, height, centerX, centerY) {
  9737. if (x2 < x1) {
  9738. var oldX1 = x1;
  9739. x1 = x2;
  9740. x2 = oldX1;
  9741. }
  9742. if (y2 < y1) {
  9743. var oldY1 = y1;
  9744. y1 = y2;
  9745. y2 = oldY1;
  9746. }
  9747. // 4 ortho extreme points
  9748. var west = [centerX - width / 2 - padding, centerY];
  9749. var east = [centerX + width / 2 + padding, centerY];
  9750. var north = [centerX, centerY - height / 2 - padding];
  9751. var south = [centerX, centerY + height / 2 + padding];
  9752. // out of bounds: return false
  9753. if (x2 < west[0]) {
  9754. return false;
  9755. }
  9756. if (x1 > east[0]) {
  9757. return false;
  9758. }
  9759. if (y1 > south[1]) {
  9760. return false;
  9761. }
  9762. if (y2 < north[1]) {
  9763. return false;
  9764. }
  9765. // 1 of 4 ortho extreme points in box: return true
  9766. if (x1 <= east[0] && east[0] <= x2
  9767. && y1 <= east[1] && east[1] <= y2) {
  9768. return true;
  9769. }
  9770. if (x1 <= west[0] && west[0] <= x2
  9771. && y1 <= west[1] && west[1] <= y2) {
  9772. return true;
  9773. }
  9774. if (x1 <= north[0] && north[0] <= x2
  9775. && y1 <= north[1] && north[1] <= y2) {
  9776. return true;
  9777. }
  9778. if (x1 <= south[0] && south[0] <= x2
  9779. && y1 <= south[1] && south[1] <= y2) {
  9780. return true;
  9781. }
  9782. // box corner in ellipse: return true
  9783. x1 = (x1 - centerX) / (width / 2 + padding);
  9784. x2 = (x2 - centerX) / (width / 2 + padding);
  9785. y1 = (y1 - centerY) / (height / 2 + padding);
  9786. y2 = (y2 - centerY) / (height / 2 + padding);
  9787. if (x1 * x1 + y1 * y1 <= 1) {
  9788. return true;
  9789. }
  9790. if (x2 * x2 + y1 * y1 <= 1) {
  9791. return true;
  9792. }
  9793. if (x2 * x2 + y2 * y2 <= 1) {
  9794. return true;
  9795. }
  9796. if (x1 * x1 + y2 * y2 <= 1) {
  9797. return true;
  9798. }
  9799. return false;
  9800. }
  9801. CanvasRenderer.prototype.boxIntersectPolygon = function(
  9802. x1, y1, x2, y2, basePoints, width, height, centerX, centerY, direction, padding) {
  9803. // console.log(arguments);
  9804. if (x2 < x1) {
  9805. var oldX1 = x1;
  9806. x1 = x2;
  9807. x2 = oldX1;
  9808. }
  9809. if (y2 < y1) {
  9810. var oldY1 = y1;
  9811. y1 = y2;
  9812. y2 = oldY1;
  9813. }
  9814. var transformedPoints = new Array(basePoints.length)
  9815. // Gives negative of angle
  9816. var angle = Math.asin(direction[1] / (Math.sqrt(direction[0] * direction[0]
  9817. + direction[1] * direction[1])));
  9818. if (direction[0] < 0) {
  9819. angle = angle + Math.PI / 2;
  9820. } else {
  9821. angle = -angle - Math.PI / 2;
  9822. }
  9823. var cos = Math.cos(-angle);
  9824. var sin = Math.sin(-angle);
  9825. for (var i = 0; i < transformedPoints.length / 2; i++) {
  9826. transformedPoints[i * 2] =
  9827. width / 2 * (basePoints[i * 2] * cos
  9828. - basePoints[i * 2 + 1] * sin);
  9829. transformedPoints[i * 2 + 1] =
  9830. height / 2 * (basePoints[i * 2 + 1] * cos
  9831. + basePoints[i * 2] * sin);
  9832. transformedPoints[i * 2] += centerX;
  9833. transformedPoints[i * 2 + 1] += centerY;
  9834. }
  9835. // Assume transformedPoints.length > 0, and check if intersection is possible
  9836. var minTransformedX = transformedPoints[0];
  9837. var maxTransformedX = transformedPoints[0];
  9838. var minTransformedY = transformedPoints[1];
  9839. var maxTransformedY = transformedPoints[1];
  9840. for (var i = 1; i < transformedPoints.length / 2; i++) {
  9841. if (transformedPoints[i * 2] > maxTransformedX) {
  9842. maxTransformedX = transformedPoints[i * 2];
  9843. }
  9844. if (transformedPoints[i * 2] < minTransformedX) {
  9845. minTransformedX = transformedPoints[i * 2];
  9846. }
  9847. if (transformedPoints[i * 2 + 1] > maxTransformedY) {
  9848. maxTransformedY = transformedPoints[i * 2 + 1];
  9849. }
  9850. if (transformedPoints[i * 2 + 1] < minTransformedY) {
  9851. minTransformedY = transformedPoints[i * 2 + 1];
  9852. }
  9853. }
  9854. if (x2 < minTransformedX - padding) {
  9855. return false;
  9856. }
  9857. if (x1 > maxTransformedX + padding) {
  9858. return false;
  9859. }
  9860. if (y2 < minTransformedY - padding) {
  9861. return false;
  9862. }
  9863. if (y1 > maxTransformedY + padding) {
  9864. return false;
  9865. }
  9866. // Continue checking with padding-corrected points
  9867. var points;
  9868. if (padding > 0) {
  9869. var expandedLineSet = renderer.expandPolygon(
  9870. transformedPoints,
  9871. -padding);
  9872. points = renderer.joinLines(expandedLineSet);
  9873. } else {
  9874. points = transformedPoints;
  9875. }
  9876. // Check if a point is in box
  9877. for (var i = 0; i < transformedPoints.length / 2; i++) {
  9878. if (x1 <= transformedPoints[i * 2]
  9879. && transformedPoints[i * 2] <= x2) {
  9880. if (y1 <= transformedPoints[i * 2 + 1]
  9881. && transformedPoints[i * 2 + 1] <= y2) {
  9882. return true;
  9883. }
  9884. }
  9885. }
  9886. // Check for intersections with the selection box
  9887. for (var i = 0; i < points.length / 2; i++) {
  9888. var currentX = points[i * 2];
  9889. var currentY = points[i * 2 + 1];
  9890. var nextX;
  9891. var nextY;
  9892. if (i < points.length / 2 - 1) {
  9893. nextX = points[(i + 1) * 2];
  9894. nextY = points[(i + 1) * 2 + 1]
  9895. } else {
  9896. nextX = points[0];
  9897. nextY = points[1];
  9898. }
  9899. // Intersection with top of selection box
  9900. if (renderer.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y1, x2, y1, false).length > 0) {
  9901. return true;
  9902. }
  9903. // Intersection with bottom of selection box
  9904. if (renderer.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y2, x2, y2, false).length > 0) {
  9905. return true;
  9906. }
  9907. // Intersection with left side of selection box
  9908. if (renderer.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y1, x1, y2, false).length > 0) {
  9909. return true;
  9910. }
  9911. // Intersection with right side of selection box
  9912. if (renderer.finiteLinesIntersect(currentX, currentY, nextX, nextY, x2, y1, x2, y2, false).length > 0) {
  9913. return true;
  9914. }
  9915. }
  9916. /*
  9917. // Check if box corner in the polygon
  9918. if (renderer.pointInsidePolygon(
  9919. x1, y1, points, 0, 0, 1, 1, 0, direction)) {
  9920. return true;
  9921. } else if (renderer.pointInsidePolygon(
  9922. x1, y2, points, 0, 0, 1, 1, 0, direction)) {
  9923. return true;
  9924. } else if (renderer.pointInsidePolygon(
  9925. x2, y2, points, 0, 0, 1, 1, 0, direction)) {
  9926. return true;
  9927. } else if (renderer.pointInsidePolygon(
  9928. x2, y1, points, 0, 0, 1, 1, 0, direction)) {
  9929. return true;
  9930. }
  9931. */
  9932. return false;
  9933. }
  9934. CanvasRenderer.prototype.polygonIntersectLine = function(
  9935. x, y, basePoints, centerX, centerY, width, height, padding) {
  9936. var intersections = [];
  9937. var intersection;
  9938. var transformedPoints = new Array(basePoints.length);
  9939. for (var i = 0; i < transformedPoints.length / 2; i++) {
  9940. transformedPoints[i * 2] = basePoints[i * 2] * width + centerX;
  9941. transformedPoints[i * 2 + 1] = basePoints[i * 2 + 1] * height + centerY;
  9942. }
  9943. var points;
  9944. if (padding > 0) {
  9945. var expandedLineSet = renderer.expandPolygon(
  9946. transformedPoints,
  9947. -padding);
  9948. points = renderer.joinLines(expandedLineSet);
  9949. } else {
  9950. points = transformedPoints;
  9951. }
  9952. // var points = transformedPoints;
  9953. var currentX, currentY, nextX, nextY;
  9954. for (var i = 0; i < points.length / 2; i++) {
  9955. currentX = points[i * 2];
  9956. currentY = points[i * 2 + 1];
  9957. if (i < points.length / 2 - 1) {
  9958. nextX = points[(i + 1) * 2];
  9959. nextY = points[(i + 1) * 2 + 1];
  9960. } else {
  9961. nextX = points[0];
  9962. nextY = points[1];
  9963. }
  9964. intersection = this.finiteLinesIntersect(
  9965. x, y, centerX, centerY,
  9966. currentX, currentY,
  9967. nextX, nextY);
  9968. if (intersection.length != 0) {
  9969. intersections.push(intersection[0], intersection[1]);
  9970. }
  9971. }
  9972. return intersections;
  9973. }
  9974. CanvasRenderer.prototype.shortenIntersection = function(
  9975. intersection, offset, amount) {
  9976. var disp = [intersection[0] - offset[0], intersection[1] - offset[1]];
  9977. var length = Math.sqrt(disp[0] * disp[0] + disp[1] * disp[1]);
  9978. var lenRatio = (length - amount) / length;
  9979. if (lenRatio < 0) {
  9980. return [];
  9981. } else {
  9982. return [offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1]];
  9983. }
  9984. }
  9985. }
  9986. // @O Arrow shapes
  9987. {
  9988. // Contract for arrow shapes:
  9989. {
  9990. // 0, 0 is arrow tip
  9991. // (0, 1) is direction towards node
  9992. // (1, 0) is right
  9993. //
  9994. // functional api:
  9995. // collide: check x, y in shape
  9996. // roughCollide: called before collide, no false negatives
  9997. // draw: draw
  9998. // spacing: dist(arrowTip, nodeBoundary)
  9999. // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip
  10000. }
  10001. // Declarations
  10002. {
  10003. arrowShapes["arrow"] = {
  10004. _points: [
  10005. -0.15, -0.3,
  10006. 0, 0,
  10007. 0.15, -0.3
  10008. ],
  10009. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10010. var points = arrowShapes["arrow"]._points;
  10011. // console.log("collide(): " + direction);
  10012. return rendFunc.pointInsidePolygon(
  10013. x, y, points, centerX, centerY, width, height, direction, padding);
  10014. },
  10015. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10016. if (typeof(arrowShapes["arrow"]._farthestPointSqDistance) == "undefined") {
  10017. arrowShapes["arrow"]._farthestPointSqDistance =
  10018. rendFunc.findMaxSqDistanceToOrigin(arrowShapes["arrow"]._points);
  10019. }
  10020. return rendFunc.checkInBoundingCircle(
  10021. x, y, arrowShapes["arrow"]._farthestPointSqDistance,
  10022. 0, width, height, centerX, centerY);
  10023. },
  10024. draw: function(context) {
  10025. var points = arrowShapes["arrow"]._points;
  10026. for (var i = 0; i < points.length / 2; i++) {
  10027. context.lineTo(points[i * 2], points[i * 2 + 1]);
  10028. }
  10029. },
  10030. spacing: function(edge) {
  10031. return 0;
  10032. },
  10033. gap: function(edge) {
  10034. return edge._private.style["width"].value * 2;
  10035. }
  10036. }
  10037. arrowShapes["triangle"] = arrowShapes["arrow"];
  10038. arrowShapes["none"] = {
  10039. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10040. return false;
  10041. },
  10042. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10043. return false;
  10044. },
  10045. draw: function(context) {
  10046. },
  10047. spacing: function(edge) {
  10048. return 0;
  10049. },
  10050. gap: function(edge) {
  10051. return 0;
  10052. }
  10053. }
  10054. arrowShapes["circle"] = {
  10055. _baseRadius: 0.15,
  10056. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10057. // Transform x, y to get non-rotated ellipse
  10058. if (width != height) {
  10059. // This gives negative of the angle
  10060. var angle = Math.asin(direction[1] /
  10061. (Math.sqrt(direction[0] * direction[0]
  10062. + direction[1] * direction[1])));
  10063. var cos = Math.cos(-angle);
  10064. var sin = Math.sin(-angle);
  10065. var rotatedPoint =
  10066. [x * cos - y * sin,
  10067. y * cos + x * sin];
  10068. var aspectRatio = (height + padding) / (width + padding);
  10069. y /= aspectRatio;
  10070. centerY /= aspectRatio;
  10071. return (Math.pow(centerX - x, 2)
  10072. + Math.pow(centerY - y, 2) <= Math.pow((width + padding)
  10073. * arrowShapes["circle"]._baseRadius, 2));
  10074. } else {
  10075. return (Math.pow(centerX - x, 2)
  10076. + Math.pow(centerY - y, 2) <= Math.pow((width + padding)
  10077. * arrowShapes["circle"]._baseRadius, 2));
  10078. }
  10079. },
  10080. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10081. return true;
  10082. },
  10083. draw: function(context) {
  10084. context.arc(0, 0, arrowShapes["circle"]._baseRadius, 0, Math.PI * 2, false);
  10085. },
  10086. spacing: function(edge) {
  10087. return rendFunc.getArrowWidth(edge._private.style["width"].value)
  10088. * arrowShapes["circle"]._baseRadius;
  10089. },
  10090. gap: function(edge) {
  10091. return edge._private.style["width"].value * 2;
  10092. }
  10093. }
  10094. arrowShapes["inhibitor"] = {
  10095. _points: [
  10096. -0.25, 0,
  10097. -0.25, -0.1,
  10098. 0.25, -0.1,
  10099. 0.25, 0
  10100. ],
  10101. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10102. var points = arrowShapes["inhibitor"]._points;
  10103. return rendFunc.pointInsidePolygon(
  10104. x, y, points, centerX, centerY, width, height, direction, padding);
  10105. },
  10106. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10107. if (typeof(arrowShapes["inhibitor"]._farthestPointSqDistance) == "undefined") {
  10108. arrowShapes["inhibitor"]._farthestPointSqDistance =
  10109. rendFunc.findMaxSqDistanceToOrigin(arrowShapes["inhibitor"]._points);
  10110. }
  10111. return rendFunc.checkInBoundingCircle(
  10112. x, y, arrowShapes["inhibitor"]._farthestPointSqDistance,
  10113. 0, width, height, centerX, centerY);
  10114. },
  10115. draw: function(context) {
  10116. var points = arrowShapes["inhibitor"]._points;
  10117. for (var i = 0; i < points.length / 2; i++) {
  10118. context.lineTo(points[i * 2], points[i * 2 + 1]);
  10119. }
  10120. },
  10121. spacing: function(edge) {
  10122. return 4;
  10123. },
  10124. gap: function(edge) {
  10125. return 4;
  10126. }
  10127. }
  10128. arrowShapes["square"] = {
  10129. _points: [
  10130. -0.12, 0.00,
  10131. 0.12, 0.00,
  10132. 0.12, -0.24,
  10133. -0.12, -0.24
  10134. ],
  10135. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10136. var points = arrowShapes["square"]._points;
  10137. return rendFunc.pointInsidePolygon(
  10138. x, y, points, centerX, centerY, width, height, direction, padding);
  10139. },
  10140. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10141. if (typeof(arrowShapes["square"]._farthestPointSqDistance) == "undefined") {
  10142. arrowShapes["square"]._farthestPointSqDistance =
  10143. rendFunc.findMaxSqDistanceToOrigin(arrowShapes["square"]._points);
  10144. }
  10145. return rendFunc.checkInBoundingCircle(
  10146. x, y, arrowShapes["square"]._farthestPointSqDistance,
  10147. 0, width, height, centerX, centerY);
  10148. },
  10149. draw: function(context) {
  10150. var points = arrowShapes["square"]._points;
  10151. for (var i = 0; i < points.length / 2; i++) {
  10152. context.lineTo(points[i * 2], points[i * 2 + 1]);
  10153. }
  10154. },
  10155. spacing: function(edge) {
  10156. return 0;
  10157. },
  10158. gap: function(edge) {
  10159. return edge._private.style["width"].value * 2;
  10160. }
  10161. }
  10162. arrowShapes["diamond"] = {
  10163. _points: [
  10164. -0.14, -0.14,
  10165. 0, -0.28,
  10166. 0.14, -0.14,
  10167. 0, 0
  10168. ],
  10169. collide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10170. var points = arrowShapes["diamond"]._points;
  10171. return rendFunc.pointInsidePolygon(
  10172. x, y, points, centerX, centerY, width, height, direction, padding);
  10173. },
  10174. roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
  10175. if (typeof(arrowShapes["diamond"]._farthestPointSqDistance) == "undefined") {
  10176. arrowShapes["diamond"]._farthestPointSqDistance =
  10177. rendFunc.findMaxSqDistanceToOrigin(arrowShapes["diamond"]._points);
  10178. }
  10179. return rendFunc.checkInBoundingCircle(
  10180. x, y, arrowShapes["diamond"]._farthestPointSqDistance,
  10181. 0, width, height, centerX, centerY);
  10182. },
  10183. draw: function(context) {
  10184. // context.translate(0, 0.16);
  10185. context.lineTo(-0.14, -0.14);
  10186. context.lineTo(0, -0.28);
  10187. context.lineTo(0.14, -0.14);
  10188. context.lineTo(0, 0.0);
  10189. },
  10190. spacing: function(edge) {
  10191. return 0;
  10192. },
  10193. gap: function(edge) {
  10194. return edge._private.style["width"].value * 2;
  10195. }
  10196. }
  10197. arrowShapes["tee"] = arrowShapes["inhibitor"];
  10198. }
  10199. // @O Arrow shape sizing (w + l)
  10200. {
  10201. CanvasRenderer.prototype.getArrowWidth = function(edgeWidth) {
  10202. return Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29);
  10203. }
  10204. CanvasRenderer.prototype.getArrowHeight = function(edgeWidth) {
  10205. return Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29);
  10206. }
  10207. }
  10208. // @O Arrow shape drawing
  10209. // Draw arrowheads on edge
  10210. CanvasRenderer.prototype.drawArrowheads = function(context, edge, drawOverlayInstead) {
  10211. if( drawOverlayInstead ){ return; } // don't do anything for overlays
  10212. // Displacement gives direction for arrowhead orientation
  10213. var dispX, dispY;
  10214. var startX = edge._private.rscratch.arrowStartX;
  10215. var startY = edge._private.rscratch.arrowStartY;
  10216. dispX = startX - edge.source().position().x;
  10217. dispY = startY - edge.source().position().y;
  10218. //this.context.strokeStyle = "rgba("
  10219. context.fillStyle = "rgba("
  10220. + edge._private.style["source-arrow-color"].value[0] + ","
  10221. + edge._private.style["source-arrow-color"].value[1] + ","
  10222. + edge._private.style["source-arrow-color"].value[2] + ","
  10223. + edge._private.style.opacity.value + ")";
  10224. context.lineWidth = edge._private.style["width"].value;
  10225. this.drawArrowShape(context, edge._private.style["source-arrow-shape"].value,
  10226. startX, startY, dispX, dispY);
  10227. var endX = edge._private.rscratch.arrowEndX;
  10228. var endY = edge._private.rscratch.arrowEndY;
  10229. dispX = endX - edge.target().position().x;
  10230. dispY = endY - edge.target().position().y;
  10231. //this.context.strokeStyle = "rgba("
  10232. context.fillStyle = "rgba("
  10233. + edge._private.style["target-arrow-color"].value[0] + ","
  10234. + edge._private.style["target-arrow-color"].value[1] + ","
  10235. + edge._private.style["target-arrow-color"].value[2] + ","
  10236. + edge._private.style.opacity.value + ")";
  10237. context.lineWidth = edge._private.style["width"].value;
  10238. this.drawArrowShape(context, edge._private.style["target-arrow-shape"].value,
  10239. endX, endY, dispX, dispY);
  10240. }
  10241. // Draw arrowshape
  10242. CanvasRenderer.prototype.drawArrowShape = function(context, shape, x, y, dispX, dispY) {
  10243. // Negative of the angle
  10244. var angle = Math.asin(dispY / (Math.sqrt(dispX * dispX + dispY * dispY)));
  10245. if (dispX < 0) {
  10246. //context.strokeStyle = "AA99AA";
  10247. angle = angle + Math.PI / 2;
  10248. } else {
  10249. //context.strokeStyle = "AAAA99";
  10250. angle = - (Math.PI / 2 + angle);
  10251. }
  10252. //context.save();
  10253. context.translate(x, y);
  10254. context.moveTo(0, 0);
  10255. context.rotate(-angle);
  10256. var size = this.getArrowWidth(context.lineWidth);
  10257. /// size = 100;
  10258. context.scale(size, size);
  10259. context.beginPath();
  10260. arrowShapes[shape].draw(context);
  10261. context.closePath();
  10262. // context.stroke();
  10263. context.fill();
  10264. context.scale(1/size, 1/size);
  10265. context.rotate(angle);
  10266. context.translate(-x, -y);
  10267. //context.restore();
  10268. }
  10269. }
  10270. // @O Node shapes
  10271. {
  10272. // Generate polygon points
  10273. var generateUnitNgonPoints = function(sides, rotationRadians) {
  10274. var increment = 1.0 / sides * 2 * Math.PI;
  10275. var startAngle = sides % 2 == 0 ?
  10276. Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0;
  10277. // console.log(nodeShapes["square"]);
  10278. startAngle += rotationRadians;
  10279. var points = new Array(sides * 2);
  10280. var currentAngle;
  10281. for (var i = 0; i < sides; i++) {
  10282. currentAngle = i * increment + startAngle;
  10283. points[2 * i] = Math.cos(currentAngle);// * (1 + i/2);
  10284. points[2 * i + 1] = Math.sin(-currentAngle);// * (1 + i/2);
  10285. }
  10286. // The above generates points for a polygon inscribed in a radius 1 circle.
  10287. // Stretch so that the maximum of the height and width becomes 2 so the resulting
  10288. // scaled shape appears to be inscribed inside a rectangle with the given
  10289. // width and height. The maximum of the width and height is used to preserve
  10290. // the shape's aspect ratio.
  10291. // Stretch width
  10292. var maxAbsX = 0
  10293. var maxAbsY = 0;
  10294. for (var i = 0; i < points.length / 2; i++) {
  10295. if (Math.abs(points[2 * i] > maxAbsX)) {
  10296. maxAbsX = Math.abs(points[2 * i]);
  10297. }
  10298. if (Math.abs(points[2 * i + 1] > maxAbsY)) {
  10299. maxAbsY = Math.abs(points[2 * i + 1]);
  10300. }
  10301. }
  10302. var minScaleLimit = 0.0005;
  10303. // Use the larger dimension to do the scale, in order to preserve the shape's
  10304. // aspect ratio
  10305. var maxDimension = Math.max(maxAbsX, maxAbsY);
  10306. for (var i = 0; i < points.length / 2; i++) {
  10307. if (maxDimension > minScaleLimit) {
  10308. points[2 * i] *= (1 / maxDimension);
  10309. points[2 * i + 1] *= (1 / maxDimension);
  10310. }
  10311. }
  10312. return points;
  10313. }
  10314. // Node shape declarations
  10315. // Contract for node shapes:
  10316. {
  10317. // Node shape contract:
  10318. //
  10319. // draw: draw
  10320. // intersectLine: report intersection from x, y, to node center
  10321. // checkPointRough: heuristic check x, y in node, no false negatives
  10322. // checkPoint: check x, y in node
  10323. }
  10324. // Declarations
  10325. {
  10326. var renderer = rendFunc;
  10327. nodeShapes["ellipse"] = {
  10328. draw: function(context, centerX, centerY, width, height) {
  10329. nodeShapes["ellipse"].drawPath(context, centerX, centerY, width, height);
  10330. context.fill();
  10331. // console.log("drawing ellipse");
  10332. // console.log(arguments);
  10333. },
  10334. drawPath: function(context, centerX, centerY, width, height) {
  10335. //context.save();
  10336. context.beginPath();
  10337. context.translate(centerX, centerY);
  10338. context.scale(width / 2, height / 2);
  10339. // At origin, radius 1, 0 to 2pi
  10340. context.arc(0, 0, 1, 0, Math.PI * 2 * 0.999, false); // *0.999 b/c chrome rendering bug on full circle
  10341. context.closePath();
  10342. context.scale(2/width, 2/height);
  10343. context.translate(-centerX, -centerY);
  10344. //context.restore();
  10345. // console.log("drawing ellipse");
  10346. // console.log(arguments);
  10347. },
  10348. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10349. var intersect = rendFunc.intersectLineEllipse(
  10350. x, y,
  10351. nodeX,
  10352. nodeY,
  10353. width / 2 + padding,
  10354. height / 2 + padding);
  10355. return intersect;
  10356. },
  10357. intersectBox: function(
  10358. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10359. return CanvasRenderer.prototype.boxIntersectEllipse(
  10360. x1, y1, x2, y2, padding, width, height, centerX, centerY);
  10361. },
  10362. checkPointRough: function(
  10363. x, y, padding, width, height, centerX, centerY) {
  10364. return true;
  10365. },
  10366. checkPoint: function(
  10367. x, y, padding, width, height, centerX, centerY) {
  10368. // console.log(arguments);
  10369. x -= centerX;
  10370. y -= centerY;
  10371. x /= (width / 2 + padding);
  10372. y /= (height / 2 + padding);
  10373. return (Math.pow(x, 2) + Math.pow(y, 2) <= 1);
  10374. }
  10375. }
  10376. nodeShapes["triangle"] = {
  10377. points: generateUnitNgonPoints(3, 0),
  10378. draw: function(context, centerX, centerY, width, height) {
  10379. renderer.drawPolygon(context,
  10380. centerX, centerY,
  10381. width, height,
  10382. nodeShapes["triangle"].points);
  10383. },
  10384. drawPath: function(context, centerX, centerY, width, height) {
  10385. renderer.drawPolygonPath(context,
  10386. centerX, centerY,
  10387. width, height,
  10388. nodeShapes["triangle"].points);
  10389. },
  10390. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10391. return renderer.polygonIntersectLine(
  10392. x, y,
  10393. nodeShapes["triangle"].points,
  10394. nodeX,
  10395. nodeY,
  10396. width / 2, height / 2,
  10397. padding);
  10398. /*
  10399. polygonIntersectLine(x, y, basePoints, centerX, centerY,
  10400. width, height, padding);
  10401. */
  10402. /*
  10403. return renderer.polygonIntersectLine(
  10404. node, width, height,
  10405. x, y, nodeShapes["triangle"].points);
  10406. */
  10407. },
  10408. intersectBox: function(
  10409. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10410. var points = nodeShapes["triangle"].points;
  10411. return renderer.boxIntersectPolygon(
  10412. x1, y1, x2, y2,
  10413. points, width, height, centerX, centerY, [0, -1], padding);
  10414. },
  10415. checkPointRough: function(
  10416. x, y, padding, width, height, centerX, centerY) {
  10417. return renderer.checkInBoundingBox(
  10418. x, y, nodeShapes["triangle"].points, // Triangle?
  10419. padding, width, height, centerX, centerY);
  10420. },
  10421. checkPoint: function(
  10422. x, y, padding, width, height, centerX, centerY) {
  10423. return renderer.pointInsidePolygon(
  10424. x, y, nodeShapes["triangle"].points,
  10425. centerX, centerY, width, height,
  10426. [0, -1], padding);
  10427. }
  10428. }
  10429. nodeShapes["square"] = {
  10430. points: generateUnitNgonPoints(4, 0),
  10431. draw: function(context, centerX, centerY, width, height) {
  10432. renderer.drawPolygon(context,
  10433. centerX, centerY,
  10434. width, height,
  10435. nodeShapes["square"].points);
  10436. },
  10437. drawPath: function(context, centerX, centerY, width, height) {
  10438. renderer.drawPolygonPath(context,
  10439. centerX, centerY,
  10440. width, height,
  10441. nodeShapes["square"].points);
  10442. },
  10443. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10444. return renderer.polygonIntersectLine(
  10445. x, y,
  10446. nodeShapes["square"].points,
  10447. nodeX,
  10448. nodeY,
  10449. width / 2, height / 2,
  10450. padding);
  10451. },
  10452. intersectBox: function(
  10453. x1, y1, x2, y2,
  10454. width, height, centerX,
  10455. centerY, padding) {
  10456. var points = nodeShapes["square"].points;
  10457. return renderer.boxIntersectPolygon(
  10458. x1, y1, x2, y2,
  10459. points, width, height, centerX,
  10460. centerY, [0, -1], padding);
  10461. },
  10462. checkPointRough: function(
  10463. x, y, padding, width, height,
  10464. centerX, centerY) {
  10465. return renderer.checkInBoundingBox(
  10466. x, y, nodeShapes["square"].points,
  10467. padding, width, height, centerX, centerY);
  10468. },
  10469. checkPoint: function(
  10470. x, y, padding, width, height, centerX, centerY) {
  10471. return renderer.pointInsidePolygon(x, y, nodeShapes["square"].points,
  10472. centerX, centerY, width, height, [0, -1], padding);
  10473. }
  10474. }
  10475. nodeShapes["rectangle"] = nodeShapes["square"];
  10476. nodeShapes["octogon"] = {};
  10477. nodeShapes["roundrectangle"] = {
  10478. points: generateUnitNgonPoints(4, 0),
  10479. draw: function(context, centerX, centerY, width, height) {
  10480. renderer.drawRoundRectangle(context,
  10481. centerX, centerY,
  10482. width, height,
  10483. 10);
  10484. },
  10485. drawPath: function(context, centerX, centerY, width, height) {
  10486. renderer.drawRoundRectanglePath(context,
  10487. centerX, centerY,
  10488. width, height,
  10489. 10);
  10490. },
  10491. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10492. return renderer.roundRectangleIntersectLine(
  10493. x, y,
  10494. nodeX,
  10495. nodeY,
  10496. width, height,
  10497. padding);
  10498. },
  10499. intersectBox: function(
  10500. x1, y1, x2, y2,
  10501. width, height, centerX,
  10502. centerY, padding) {
  10503. return renderer.roundRectangleIntersectBox(
  10504. x1, y1, x2, y2,
  10505. width, height, centerX, centerY, padding);
  10506. },
  10507. checkPointRough: function(
  10508. x, y, padding, width, height,
  10509. centerX, centerY) {
  10510. // This check is OK because it assumes the round rectangle
  10511. // has sharp edges for the rough check
  10512. return renderer.checkInBoundingBox(
  10513. x, y, nodeShapes["roundrectangle"].points,
  10514. padding, width, height, centerX, centerY);
  10515. },
  10516. // Looks like the width passed into this function is actually the total width / 2
  10517. checkPoint: function(
  10518. x, y, padding, width, height, centerX, centerY) {
  10519. var cornerRadius = renderer.getRoundRectangleRadius(width, height);
  10520. // Check hBox
  10521. if (renderer.pointInsidePolygon(x, y, nodeShapes["roundrectangle"].points,
  10522. centerX, centerY, width, height - 2 * cornerRadius, [0, -1], padding)) {
  10523. return true;
  10524. }
  10525. // Check vBox
  10526. if (renderer.pointInsidePolygon(x, y, nodeShapes["roundrectangle"].points,
  10527. centerX, centerY, width - 2 * cornerRadius, height, [0, -1], padding)) {
  10528. return true;
  10529. }
  10530. var checkInEllipse = function(x, y, centerX, centerY, width, height, padding) {
  10531. x -= centerX;
  10532. y -= centerY;
  10533. x /= (width / 2 + padding);
  10534. y /= (height / 2 + padding);
  10535. return (Math.pow(x, 2) + Math.pow(y, 2) <= 1);
  10536. }
  10537. // Check top left quarter circle
  10538. if (checkInEllipse(x, y,
  10539. centerX - width / 2 + cornerRadius,
  10540. centerY - height / 2 + cornerRadius,
  10541. cornerRadius * 2, cornerRadius * 2, padding)) {
  10542. return true;
  10543. }
  10544. /*
  10545. if (renderer.boxIntersectEllipse(x, y, x, y, padding,
  10546. cornerRadius * 2, cornerRadius * 2,
  10547. centerX - width + cornerRadius,
  10548. centerY - height + cornerRadius)) {
  10549. return true;
  10550. }
  10551. */
  10552. // Check top right quarter circle
  10553. if (checkInEllipse(x, y,
  10554. centerX + width / 2 - cornerRadius,
  10555. centerY - height / 2 + cornerRadius,
  10556. cornerRadius * 2, cornerRadius * 2, padding)) {
  10557. return true;
  10558. }
  10559. // Check bottom right quarter circle
  10560. if (checkInEllipse(x, y,
  10561. centerX + width / 2 - cornerRadius,
  10562. centerY + height / 2 - cornerRadius,
  10563. cornerRadius * 2, cornerRadius * 2, padding)) {
  10564. return true;
  10565. }
  10566. // Check bottom left quarter circle
  10567. if (checkInEllipse(x, y,
  10568. centerX - width / 2 + cornerRadius,
  10569. centerY + height / 2 - cornerRadius,
  10570. cornerRadius * 2, cornerRadius * 2, padding)) {
  10571. return true;
  10572. }
  10573. return false;
  10574. }
  10575. };
  10576. nodeShapes["roundrectangle2"] = {
  10577. roundness: 4.99,
  10578. draw: function(node, width, height) {
  10579. if (width <= roundness * 2) {
  10580. return;
  10581. }
  10582. renderer.drawPolygon(node._private.position.x,
  10583. node._private.position.y, width, height, nodeSapes["roundrectangle2"].points);
  10584. },
  10585. intersectLine: function(node, width, height, x, y) {
  10586. return renderer.findPolygonIntersection(
  10587. node, width, height, x, y, nodeShapes["square"].points);
  10588. },
  10589. // TODO: Treat rectangle as sharp-cornered for now. This is a not-large approximation.
  10590. intersectBox: function(x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10591. var points = nodeShapes["square"].points;
  10592. /*
  10593. return renderer.boxIntersectPolygon(
  10594. x1, y1, x2, y2,
  10595. points,
  10596. */
  10597. }
  10598. }
  10599. /*
  10600. function PolygonNodeShape(points) {
  10601. this.points = points;
  10602. this.draw = function(context, node, width, height) {
  10603. renderer.drawPolygon(context,
  10604. node._private.position.x,
  10605. node._private.position.y,
  10606. width, height, nodeShapes["pentagon"].points);
  10607. };
  10608. this.drawPath =
  10609. }
  10610. */
  10611. nodeShapes["pentagon"] = {
  10612. points: generateUnitNgonPoints(5, 0),
  10613. draw: function(context, centerX, centerY, width, height) {
  10614. renderer.drawPolygon(context,
  10615. centerX, centerY,
  10616. width, height, nodeShapes["pentagon"].points);
  10617. },
  10618. drawPath: function(context, centerX, centerY, width, height) {
  10619. renderer.drawPolygonPath(context,
  10620. centerX, centerY,
  10621. width, height, nodeShapes["pentagon"].points);
  10622. },
  10623. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10624. return renderer.polygonIntersectLine(
  10625. x, y,
  10626. nodeShapes["pentagon"].points,
  10627. nodeX,
  10628. nodeY,
  10629. width / 2, height / 2,
  10630. padding);
  10631. },
  10632. intersectBox: function(
  10633. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10634. var points = nodeShapes["pentagon"].points;
  10635. return renderer.boxIntersectPolygon(
  10636. x1, y1, x2, y2,
  10637. points, width, height, centerX, centerY, [0, -1], padding);
  10638. },
  10639. checkPointRough: function(
  10640. x, y, padding, width, height, centerX, centerY) {
  10641. return renderer.checkInBoundingBox(
  10642. x, y, nodeShapes["pentagon"].points,
  10643. padding, width, height, centerX, centerY);
  10644. },
  10645. checkPoint: function(
  10646. x, y, padding, width, height, centerX, centerY) {
  10647. return renderer.pointInsidePolygon(x, y, nodeShapes["pentagon"].points,
  10648. centerX, centerY, width, height, [0, -1], padding);
  10649. }
  10650. }
  10651. nodeShapes["hexagon"] = {
  10652. points: generateUnitNgonPoints(6, 0),
  10653. draw: function(context, centerX, centerY, width, height) {
  10654. renderer.drawPolygon(context,
  10655. centerX, centerY,
  10656. width, height,
  10657. nodeShapes["hexagon"].points);
  10658. },
  10659. drawPath: function(context, centerX, centerY, width, height) {
  10660. renderer.drawPolygonPath(context,
  10661. centerX, centerY,
  10662. width, height,
  10663. nodeShapes["hexagon"].points);
  10664. },
  10665. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10666. return renderer.polygonIntersectLine(
  10667. x, y,
  10668. nodeShapes["hexagon"].points,
  10669. nodeX,
  10670. nodeY,
  10671. width / 2, height / 2,
  10672. padding);
  10673. },
  10674. intersectBox: function(
  10675. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10676. var points = nodeShapes["hexagon"].points;
  10677. return renderer.boxIntersectPolygon(
  10678. x1, y1, x2, y2,
  10679. points, width, height, centerX, centerY, [0, -1], padding);
  10680. },
  10681. checkPointRough: function(
  10682. x, y, padding, width, height, centerX, centerY) {
  10683. return renderer.checkInBoundingBox(
  10684. x, y, nodeShapes["hexagon"].points,
  10685. padding, width, height, centerX, centerY);
  10686. },
  10687. checkPoint: function(
  10688. x, y, padding, width, height, centerX, centerY) {
  10689. return renderer.pointInsidePolygon(x, y, nodeShapes["hexagon"].points,
  10690. centerX, centerY, width, height, [0, -1], padding);
  10691. }
  10692. }
  10693. nodeShapes["heptagon"] = {
  10694. points: generateUnitNgonPoints(7, 0),
  10695. draw: function(context, centerX, centerY, width, height) {
  10696. renderer.drawPolygon(context,
  10697. centerX, centerY,
  10698. width, height,
  10699. nodeShapes["heptagon"].points);
  10700. },
  10701. drawPath: function(context, centerX, centerY, width, height) {
  10702. renderer.drawPolygonPath(context,
  10703. centerX, centerY,
  10704. width, height,
  10705. nodeShapes["heptagon"].points);
  10706. },
  10707. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10708. return renderer.polygonIntersectLine(
  10709. x, y,
  10710. nodeShapes["heptagon"].points,
  10711. nodeX,
  10712. nodeY,
  10713. width / 2, height / 2,
  10714. padding);
  10715. },
  10716. intersectBox: function(
  10717. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10718. var points = nodeShapes["heptagon"].points;
  10719. return renderer.boxIntersectPolygon(
  10720. x1, y1, x2, y2,
  10721. points, width, height, centerX, centerY, [0, -1], padding);
  10722. },
  10723. checkPointRough: function(
  10724. x, y, padding, width, height, centerX, centerY) {
  10725. return renderer.checkInBoundingBox(
  10726. x, y, nodeShapes["heptagon"].points,
  10727. padding, width, height, centerX, centerY);
  10728. },
  10729. checkPoint: function(
  10730. x, y, padding, width, height, centerX, centerY) {
  10731. return renderer.pointInsidePolygon(x, y, nodeShapes["heptagon"].points,
  10732. centerX, centerY, width, height, [0, -1], padding);
  10733. }
  10734. }
  10735. nodeShapes["octagon"] = {
  10736. points: generateUnitNgonPoints(8, 0),
  10737. draw: function(context, centerX, centerY, width, height) {
  10738. renderer.drawPolygon(context,
  10739. centerX, centerY,
  10740. width, height,
  10741. nodeShapes["octagon"].points);
  10742. },
  10743. drawPath: function(context, centerX, centerY, width, height) {
  10744. renderer.drawPolygonPath(context,
  10745. centerX, centerY,
  10746. width, height,
  10747. nodeShapes["octagon"].points);
  10748. },
  10749. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10750. return renderer.polygonIntersectLine(
  10751. x, y,
  10752. nodeShapes["octagon"].points,
  10753. nodeX,
  10754. nodeY,
  10755. width / 2, height / 2,
  10756. padding);
  10757. },
  10758. intersectBox: function(
  10759. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10760. var points = nodeShapes["octagon"].points;
  10761. return renderer.boxIntersectPolygon(
  10762. x1, y1, x2, y2,
  10763. points, width, height, centerX, centerY, [0, -1], padding);
  10764. },
  10765. checkPointRough: function(
  10766. x, y, padding, width, height, centerX, centerY) {
  10767. return renderer.checkInBoundingBox(
  10768. x, y, nodeShapes["octagon"].points,
  10769. padding, width, height, centerX, centerY);
  10770. },
  10771. checkPoint: function(
  10772. x, y, padding, width, height, centerX, centerY) {
  10773. return renderer.pointInsidePolygon(x, y, nodeShapes["octagon"].points,
  10774. centerX, centerY, width, height, [0, -1], padding);
  10775. }
  10776. };
  10777. var star5Points = new Array(20);
  10778. {
  10779. var outerPoints = generateUnitNgonPoints(5, 0);
  10780. var innerPoints = generateUnitNgonPoints(5, Math.PI / 5);
  10781. // console.log(outerPoints);
  10782. // console.log(innerPoints);
  10783. // Outer radius is 1; inner radius of star is smaller
  10784. var innerRadius = 0.5 * (3 - Math.sqrt(5));
  10785. innerRadius *= 1.57;
  10786. for (var i=0;i<innerPoints.length/2;i++) {
  10787. innerPoints[i*2] *= innerRadius;
  10788. innerPoints[i*2+1] *= innerRadius;
  10789. }
  10790. for (var i=0;i<20/4;i++) {
  10791. star5Points[i*4] = outerPoints[i*2];
  10792. star5Points[i*4+1] = outerPoints[i*2+1];
  10793. star5Points[i*4+2] = innerPoints[i*2];
  10794. star5Points[i*4+3] = innerPoints[i*2+1];
  10795. }
  10796. // console.log(star5Points);
  10797. }
  10798. nodeShapes["star5"] = {
  10799. points: star5Points,
  10800. draw: function(context, centerX, centerY, width, height) {
  10801. renderer.drawPolygon(context,
  10802. centerX, centerY,
  10803. width, height,
  10804. nodeShapes["star5"].points);
  10805. },
  10806. drawPath: function(context, centerX, centerY, width, height) {
  10807. renderer.drawPolygonPath(context,
  10808. centerX, centerY,
  10809. width, height,
  10810. nodeShapes["star5"].points);
  10811. },
  10812. intersectLine: function(nodeX, nodeY, width, height, x, y, padding) {
  10813. return renderer.polygonIntersectLine(
  10814. x, y,
  10815. nodeShapes["star5"].points,
  10816. nodeX,
  10817. nodeY,
  10818. width / 2, height / 2,
  10819. padding);
  10820. },
  10821. intersectBox: function(
  10822. x1, y1, x2, y2, width, height, centerX, centerY, padding) {
  10823. var points = nodeShapes["star5"].points;
  10824. return renderer.boxIntersectPolygon(
  10825. x1, y1, x2, y2,
  10826. points, width, height, centerX, centerY, [0, -1], padding);
  10827. },
  10828. checkPointRough: function(
  10829. x, y, padding, width, height, centerX, centerY) {
  10830. return renderer.checkInBoundingBox(
  10831. x, y, nodeShapes["star5"].points,
  10832. padding, width, height, centerX, centerY);
  10833. },
  10834. checkPoint: function(
  10835. x, y, padding, width, height, centerX, centerY) {
  10836. return renderer.pointInsidePolygon(x, y, nodeShapes["star5"].points,
  10837. centerX, centerY, width, height, [0, -1], padding);
  10838. }
  10839. };
  10840. }
  10841. }
  10842. // @O Polygon calculations
  10843. {
  10844. CanvasRenderer.prototype.expandPolygon = function(points, pad) {
  10845. var expandedLineSet = new Array(points.length * 2);
  10846. var currentPointX, currentPointY, nextPointX, nextPointY;
  10847. for (var i = 0; i < points.length / 2; i++) {
  10848. currentPointX = points[i * 2];
  10849. currentPointY = points[i * 2 + 1];
  10850. if (i < points.length / 2 - 1) {
  10851. nextPointX = points[(i + 1) * 2];
  10852. nextPointY = points[(i + 1) * 2 + 1];
  10853. } else {
  10854. nextPointX = points[0];
  10855. nextPointY = points[1];
  10856. }
  10857. // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY]
  10858. // Assume CCW polygon winding
  10859. var offsetX = (nextPointY - currentPointY);
  10860. var offsetY = -(nextPointX - currentPointX);
  10861. // Normalize
  10862. var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
  10863. var normalizedOffsetX = offsetX / offsetLength;
  10864. var normalizedOffsetY = offsetY / offsetLength;
  10865. expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad;
  10866. expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad;
  10867. expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad;
  10868. expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad;
  10869. }
  10870. return expandedLineSet;
  10871. }
  10872. CanvasRenderer.prototype.joinLines = function(lineSet) {
  10873. var vertices = new Array(lineSet.length / 2);
  10874. var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY;
  10875. var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY;
  10876. for (var i = 0; i < lineSet.length / 4; i++) {
  10877. currentLineStartX = lineSet[i * 4];
  10878. currentLineStartY = lineSet[i * 4 + 1];
  10879. currentLineEndX = lineSet[i * 4 + 2];
  10880. currentLineEndY = lineSet[i * 4 + 3];
  10881. if (i < lineSet.length / 4 - 1) {
  10882. nextLineStartX = lineSet[(i + 1) * 4];
  10883. nextLineStartY = lineSet[(i + 1) * 4 + 1];
  10884. nextLineEndX = lineSet[(i + 1) * 4 + 2];
  10885. nextLineEndY = lineSet[(i + 1) * 4 + 3];
  10886. } else {
  10887. nextLineStartX = lineSet[0];
  10888. nextLineStartY = lineSet[1];
  10889. nextLineEndX = lineSet[2];
  10890. nextLineEndY = lineSet[3];
  10891. }
  10892. var intersection = this.finiteLinesIntersect(
  10893. currentLineStartX, currentLineStartY,
  10894. currentLineEndX, currentLineEndY,
  10895. nextLineStartX, nextLineStartY,
  10896. nextLineEndX, nextLineEndY,
  10897. true);
  10898. vertices[i * 2] = intersection[0];
  10899. vertices[i * 2 + 1] = intersection[1];
  10900. }
  10901. return vertices;
  10902. }
  10903. CanvasRenderer.prototype.pointInsidePolygon = function(
  10904. x, y, basePoints, centerX, centerY, width, height, direction, padding) {
  10905. //var direction = arguments[6];
  10906. var transformedPoints = new Array(basePoints.length)
  10907. // Gives negative angle
  10908. var angle = Math.asin(direction[1] / (Math.sqrt(direction[0] * direction[0]
  10909. + direction[1] * direction[1])));
  10910. if (direction[0] < 0) {
  10911. angle = angle + Math.PI / 2;
  10912. } else {
  10913. angle = -angle - Math.PI / 2;
  10914. }
  10915. var cos = Math.cos(-angle);
  10916. var sin = Math.sin(-angle);
  10917. // console.log("base: " + basePoints);
  10918. for (var i = 0; i < transformedPoints.length / 2; i++) {
  10919. transformedPoints[i * 2] =
  10920. width / 2 * (basePoints[i * 2] * cos
  10921. - basePoints[i * 2 + 1] * sin);
  10922. transformedPoints[i * 2 + 1] =
  10923. height / 2 * (basePoints[i * 2 + 1] * cos
  10924. + basePoints[i * 2] * sin);
  10925. transformedPoints[i * 2] += centerX;
  10926. transformedPoints[i * 2 + 1] += centerY;
  10927. }
  10928. var points;
  10929. if (padding > 0) {
  10930. var expandedLineSet = renderer.expandPolygon(
  10931. transformedPoints,
  10932. -padding);
  10933. points = renderer.joinLines(expandedLineSet);
  10934. } else {
  10935. points = transformedPoints;
  10936. }
  10937. var x1, y1, x2, y2;
  10938. var y3;
  10939. // Intersect with vertical line through (x, y)
  10940. var up = 0;
  10941. var down = 0;
  10942. for (var i = 0; i < points.length / 2; i++) {
  10943. x1 = points[i * 2];
  10944. y1 = points[i * 2 + 1];
  10945. if (i + 1 < points.length / 2) {
  10946. x2 = points[(i + 1) * 2];
  10947. y2 = points[(i + 1) * 2 + 1];
  10948. } else {
  10949. x2 = points[(i + 1 - points.length / 2) * 2];
  10950. y2 = points[(i + 1 - points.length / 2) * 2 + 1];
  10951. }
  10952. //* console.log("line from (" + x1 + ", " + y1 + ") to (" + x2 + ", " + y2 + ")");
  10953. //& console.log(x1, x, x2);
  10954. if (x1 == x && x2 == x) {
  10955. } else if ((x1 >= x && x >= x2)
  10956. || (x1 <= x && x <= x2)) {
  10957. y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1;
  10958. if (y3 > y) {
  10959. up++;
  10960. }
  10961. if (y3 < y) {
  10962. down++;
  10963. }
  10964. //* console.log(y3, y);
  10965. } else {
  10966. //* console.log("22");
  10967. continue;
  10968. }
  10969. }
  10970. //* console.log("up: " + up + ", down: " + down);
  10971. if (up % 2 == 0) {
  10972. return false;
  10973. } else {
  10974. return true;
  10975. }
  10976. }
  10977. }
  10978. // @O Polygon drawing
  10979. CanvasRenderer.prototype.drawPolygonPath = function(
  10980. context, x, y, width, height, points) {
  10981. //context.save();
  10982. context.translate(x, y);
  10983. context.scale(width / 2, height / 2);
  10984. context.beginPath();
  10985. context.moveTo(points[0], points[1]);
  10986. for (var i = 1; i < points.length / 2; i++) {
  10987. context.lineTo(points[i * 2], points[i * 2 + 1]);
  10988. }
  10989. context.closePath();
  10990. context.scale(2/width, 2/height);
  10991. context.translate(-x, -y);
  10992. // context.restore();
  10993. }
  10994. CanvasRenderer.prototype.drawPolygon = function(
  10995. context, x, y, width, height, points) {
  10996. // Draw path
  10997. this.drawPolygonPath(context, x, y, width, height, points);
  10998. // Fill path
  10999. context.fill();
  11000. }
  11001. CanvasRenderer.prototype.getRoundRectangleRadius = function(width, height) {
  11002. // Set the default radius, unless half of width or height is smaller than default
  11003. return Math.min(width / 2, height / 2, 10);
  11004. }
  11005. // Round rectangle drawing
  11006. CanvasRenderer.prototype.drawRoundRectanglePath = function(
  11007. context, x, y, width, height, radius) {
  11008. var halfWidth = width / 2;
  11009. var halfHeight = height / 2;
  11010. var cornerRadius = this.getRoundRectangleRadius(width, height);
  11011. context.translate(x, y);
  11012. context.beginPath();
  11013. // Start at top middle
  11014. context.moveTo(0, -halfHeight);
  11015. // Arc from middle top to right side
  11016. context.arcTo(halfWidth, -halfHeight, halfWidth, 0, cornerRadius);
  11017. // Arc from right side to bottom
  11018. context.arcTo(halfWidth, halfHeight, 0, halfHeight, cornerRadius);
  11019. // Arc from bottom to left side
  11020. context.arcTo(-halfWidth, halfHeight, -halfWidth, 0, cornerRadius);
  11021. // Arc from left side to topBorder
  11022. context.arcTo(-halfWidth, -halfHeight, 0, -halfHeight, cornerRadius);
  11023. // Join line
  11024. context.lineTo(0, -halfHeight);
  11025. /*
  11026. void arc(unrestricted double x,
  11027. unrestricted double y,
  11028. unrestricted double radius,
  11029. unrestricted double startAngle,
  11030. unrestricted double endAngle,
  11031. optional boolean anticlockwise = false);
  11032. */
  11033. /*
  11034. context.arc(-width / 2 + cornerRadius,
  11035. -height / 2 + cornerRadius,
  11036. cornerRadius,
  11037. 0,
  11038. Math.PI * 2 * 0.999);
  11039. */
  11040. context.closePath();
  11041. context.translate(-x, -y);
  11042. }
  11043. CanvasRenderer.prototype.drawRoundRectangle = function(
  11044. context, x, y, width, height, radius) {
  11045. this.drawRoundRectanglePath(context, x, y, width, height, radius);
  11046. context.fill();
  11047. }
  11048. CanvasRenderer.prototype.roundRectangleIntersectLine = function(
  11049. x, y, nodeX, nodeY, width, height, padding) {
  11050. var cornerRadius = this.getRoundRectangleRadius(width, height);
  11051. var halfWidth = width / 2;
  11052. var halfHeight = height / 2;
  11053. // Check intersections with straight line segments
  11054. var straightLineIntersections;
  11055. // Top segment, left to right
  11056. {
  11057. var topStartX = nodeX - halfWidth + cornerRadius - padding;
  11058. var topStartY = nodeY - halfHeight - padding;
  11059. var topEndX = nodeX + halfWidth - cornerRadius + padding;
  11060. var topEndY = topStartY;
  11061. straightLineIntersections = this.finiteLinesIntersect(
  11062. x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false);
  11063. if (straightLineIntersections.length > 0) {
  11064. return straightLineIntersections;
  11065. }
  11066. }
  11067. // Right segment, top to bottom
  11068. {
  11069. var rightStartX = nodeX + halfWidth + padding;
  11070. var rightStartY = nodeY - halfHeight + cornerRadius - padding;
  11071. var rightEndX = rightStartX;
  11072. var rightEndY = nodeY + halfHeight - cornerRadius + padding;
  11073. straightLineIntersections = this.finiteLinesIntersect(
  11074. x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false);
  11075. if (straightLineIntersections.length > 0) {
  11076. return straightLineIntersections;
  11077. }
  11078. }
  11079. // Bottom segment, left to right
  11080. {
  11081. var bottomStartX = nodeX - halfWidth + cornerRadius - padding;
  11082. var bottomStartY = nodeY + halfHeight + padding;
  11083. var bottomEndX = nodeX + halfWidth - cornerRadius + padding;
  11084. var bottomEndY = bottomStartY;
  11085. straightLineIntersections = this.finiteLinesIntersect(
  11086. x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false);
  11087. if (straightLineIntersections.length > 0) {
  11088. return straightLineIntersections;
  11089. }
  11090. }
  11091. // Left segment, top to bottom
  11092. {
  11093. var leftStartX = nodeX - halfWidth - padding;
  11094. var leftStartY = nodeY - halfHeight + cornerRadius - padding;
  11095. var leftEndX = leftStartX;
  11096. var leftEndY = nodeY + halfHeight - cornerRadius + padding;
  11097. straightLineIntersections = this.finiteLinesIntersect(
  11098. x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false);
  11099. if (straightLineIntersections.length > 0) {
  11100. return straightLineIntersections;
  11101. }
  11102. }
  11103. // Check intersections with arc segments
  11104. var arcIntersections;
  11105. // Top Left
  11106. {
  11107. var topLeftCenterX = nodeX - halfWidth + cornerRadius;
  11108. var topLeftCenterY = nodeY - halfHeight + cornerRadius
  11109. arcIntersections = this.intersectLineCircle(
  11110. x, y, nodeX, nodeY,
  11111. topLeftCenterX, topLeftCenterY, cornerRadius + padding);
  11112. // Ensure the intersection is on the desired quarter of the circle
  11113. if (arcIntersections.length > 0
  11114. && arcIntersections[0] <= topLeftCenterX
  11115. && arcIntersections[1] <= topLeftCenterY) {
  11116. return [arcIntersections[0], arcIntersections[1]];
  11117. }
  11118. }
  11119. // Top Right
  11120. {
  11121. var topRightCenterX = nodeX + halfWidth - cornerRadius;
  11122. var topRightCenterY = nodeY - halfHeight + cornerRadius
  11123. arcIntersections = this.intersectLineCircle(
  11124. x, y, nodeX, nodeY,
  11125. topRightCenterX, topRightCenterY, cornerRadius + padding);
  11126. // Ensure the intersection is on the desired quarter of the circle
  11127. if (arcIntersections.length > 0
  11128. && arcIntersections[0] >= topRightCenterX
  11129. && arcIntersections[1] <= topRightCenterY) {
  11130. return [arcIntersections[0], arcIntersections[1]];
  11131. }
  11132. }
  11133. // Bottom Right
  11134. {
  11135. var bottomRightCenterX = nodeX + halfWidth - cornerRadius;
  11136. var bottomRightCenterY = nodeY + halfHeight - cornerRadius
  11137. arcIntersections = this.intersectLineCircle(
  11138. x, y, nodeX, nodeY,
  11139. bottomRightCenterX, bottomRightCenterY, cornerRadius + padding);
  11140. // Ensure the intersection is on the desired quarter of the circle
  11141. if (arcIntersections.length > 0
  11142. && arcIntersections[0] >= bottomRightCenterX
  11143. && arcIntersections[1] >= bottomRightCenterY) {
  11144. return [arcIntersections[0], arcIntersections[1]];
  11145. }
  11146. }
  11147. // Bottom Left
  11148. {
  11149. var bottomLeftCenterX = nodeX - halfWidth + cornerRadius;
  11150. var bottomLeftCenterY = nodeY + halfHeight - cornerRadius
  11151. arcIntersections = this.intersectLineCircle(
  11152. x, y, nodeX, nodeY,
  11153. bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding);
  11154. // Ensure the intersection is on the desired quarter of the circle
  11155. if (arcIntersections.length > 0
  11156. && arcIntersections[0] <= bottomLeftCenterX
  11157. && arcIntersections[1] >= bottomLeftCenterY) {
  11158. return [arcIntersections[0], arcIntersections[1]];
  11159. }
  11160. }
  11161. }
  11162. CanvasRenderer.prototype.roundRectangleIntersectBox = function(
  11163. boxX1, boxY1, boxX2, boxY2, width, height, centerX, centerY, padding) {
  11164. // We have the following shpae
  11165. // _____
  11166. // _| |_
  11167. // | |
  11168. // |_ _|
  11169. // |_____|
  11170. //
  11171. // With a quarter circle at each corner.
  11172. var cornerRadius = this.getRoundRectangleRadius(width, height);
  11173. var hBoxTopLeftX = centerX - width / 2 - padding;
  11174. var hBoxTopLeftY = centerY - height / 2 + cornerRadius - padding;
  11175. var hBoxBottomRightX = centerX + width / 2 + padding;
  11176. var hBoxBottomRightY = centerY + height / 2 - cornerRadius + padding;
  11177. var vBoxTopLeftX = centerX - width / 2 + cornerRadius - padding;
  11178. var vBoxTopLeftY = centerY - height / 2 - padding;
  11179. var vBoxBottomRightX = centerX + width / 2 - cornerRadius + padding;
  11180. var vBoxBottomRightY = centerY + height / 2 + padding;
  11181. // Check if the box is out of bounds
  11182. var boxMinX = Math.min(boxX1, boxX2);
  11183. var boxMaxX = Math.max(boxX1, boxX2);
  11184. var boxMinY = Math.min(boxY1, boxY2);
  11185. var boxMaxY = Math.max(boxY1, boxY2);
  11186. if (boxMaxX < hBoxTopLeftX) {
  11187. return false;
  11188. } else if (boxMinX > hBoxBottomRightX) {
  11189. return false;
  11190. }
  11191. if (boxMaxY < vBoxTopLeftY) {
  11192. return false;
  11193. } else if (boxMinY > vBoxBottomRightY) {
  11194. return false;
  11195. }
  11196. // Check if an hBox point is in given box
  11197. if (hBoxTopLeftX >= boxMinX && hBoxTopLeftX <= boxMaxX
  11198. && hBoxTopLeftY >= boxMinY && hBoxTopLeftY <= boxMaxY) {
  11199. return true;
  11200. }
  11201. if (hBoxBottomRightX >= boxMinX && hBoxBottomRightX <= boxMaxX
  11202. && hBoxTopLeftY >= boxMinY && hBoxTopLeftY <= boxMaxY) {
  11203. return true;
  11204. }
  11205. if (hBoxBottomRightX >= boxMinX && hBoxBottomRightX <= boxMaxX
  11206. && hBoxBottomRightY >= boxMinY && hBoxBottomRightY <= boxMaxY) {
  11207. return true;
  11208. }
  11209. if (hBoxTopLeftX >= boxMinX && hBoxTopLeftX <= boxMaxX
  11210. && hBoxBottomRightY >= boxMinY && hBoxBottomRightY <= boxMaxY) {
  11211. return true;
  11212. }
  11213. // Check if a given point box is in the hBox
  11214. if (boxMinX >= hBoxTopLeftX && boxMinX <= hBoxBottomRightX
  11215. && boxMinY >= hBoxTopLeftY && boxMinY <= hBoxBottomRightY) {
  11216. return true;
  11217. }
  11218. if (boxMaxX >= hBoxTopLeftX && boxMaxX <= hBoxBottomRightX
  11219. && boxMinY >= hBoxTopLeftY && boxMinY <= hBoxBottomRightY) {
  11220. return true;
  11221. }
  11222. if (boxMaxX >= hBoxTopLeftX && boxMaxX <= hBoxBottomRightX
  11223. && boxMaxY >= hBoxTopLeftY && boxMaxY <= hBoxBottomRightY) {
  11224. return true;
  11225. }
  11226. if (boxMinX >= hBoxTopLeftX && boxMinX <= hBoxBottomRightX
  11227. && boxMaxY >= hBoxTopLeftY && boxMaxY <= hBoxBottomRightY) {
  11228. return true;
  11229. }
  11230. // Check if an vBox point is in given box
  11231. if (vBoxTopLeftX >= boxMinX && vBoxTopLeftX <= boxMaxX
  11232. && vBoxTopLeftY >= boxMinY && vBoxTopLeftY <= boxMaxY) {
  11233. return true;
  11234. }
  11235. if (vBoxBottomRightX >= boxMinX && vBoxBottomRightX <= boxMaxX
  11236. && vBoxTopLeftY >= boxMinY && vBoxTopLeftY <= boxMaxY) {
  11237. return true;
  11238. }
  11239. if (vBoxBottomRightX >= boxMinX && vBoxBottomRightX <= boxMaxX
  11240. && vBoxBottomRightY >= boxMinY && vBoxBottomRightY <= boxMaxY) {
  11241. return true;
  11242. }
  11243. if (vBoxTopLeftX >= boxMinX && vBoxTopLeftX <= boxMaxX
  11244. && vBoxBottomRightY >= boxMinY && vBoxBottomRightY <= boxMaxY) {
  11245. return true;
  11246. }
  11247. // Check if a given point box is in the vBox
  11248. if (boxMinX >= vBoxTopLeftX && boxMinX <= vBoxBottomRightX
  11249. && boxMinY >= vBoxTopLeftY && boxMinY <= vBoxBottomRightY) {
  11250. return true;
  11251. }
  11252. if (boxMaxX >= vBoxTopLeftX && boxMaxX <= vBoxBottomRightX
  11253. && boxMinY >= vBoxTopLeftY && boxMinY <= vBoxBottomRightY) {
  11254. return true;
  11255. }
  11256. if (boxMaxX >= vBoxTopLeftX && boxMaxX <= vBoxBottomRightX
  11257. && boxMaxY >= vBoxTopLeftY && boxMaxY <= vBoxBottomRightY) {
  11258. return true;
  11259. }
  11260. if (boxMinX >= vBoxTopLeftX && boxMinX <= vBoxBottomRightX
  11261. && boxMaxY >= vBoxTopLeftY && boxMaxY <= vBoxBottomRightY) {
  11262. return true;
  11263. }
  11264. // Lastly, check if one of the ellipses coincide with the box
  11265. if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
  11266. cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxTopLeftY + padding)) {
  11267. return true;
  11268. }
  11269. if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
  11270. cornerRadius * 2, cornerRadius * 2, vBoxBottomRightX - padding, hBoxTopLeftY + padding)) {
  11271. return true;
  11272. }
  11273. if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
  11274. cornerRadius * 2, cornerRadius * 2, vBoxBottomRightX - padding, hBoxBottomRightY - padding)) {
  11275. return true;
  11276. }
  11277. if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
  11278. cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxBottomRightY - padding)) {
  11279. return true;
  11280. }
  11281. return false;
  11282. }
  11283. // @O Approximate collision functions
  11284. CanvasRenderer.prototype.checkInBoundingCircle = function(
  11285. x, y, farthestPointSqDistance, padding, width, height, centerX, centerY) {
  11286. x = (x - centerX) / (width + padding);
  11287. y = (y - centerY) / (height + padding);
  11288. return (x * x + y * y) <= farthestPointSqDistance;
  11289. }
  11290. CanvasRenderer.prototype.checkInBoundingBox = function(
  11291. x, y, points, padding, width, height, centerX, centerY) {
  11292. // Assumes width, height >= 0, points.length > 0
  11293. var minX = points[0], minY = points[1];
  11294. var maxX = points[0], maxY = points[1];
  11295. for (var i = 1; i < points.length / 2; i++) {
  11296. if (points[i * 2] < minX) {
  11297. minX = points[i * 2];
  11298. } else if (points[i * 2] > maxX) {
  11299. maxX = points[i * 2];
  11300. }
  11301. if (points[i * 2 + 1] < minY) {
  11302. minY = points[i * 2 + 1];
  11303. } else if (points[i * 2 + 1] > maxY) {
  11304. maxY = points[i * 2 + 1];
  11305. }
  11306. }
  11307. x -= centerX;
  11308. y -= centerY;
  11309. x /= width;
  11310. y /= height;
  11311. if (x < minX) {
  11312. return false;
  11313. } else if (x > maxX) {
  11314. return false;
  11315. }
  11316. if (y < minY) {
  11317. return false;
  11318. } else if (y > maxY) {
  11319. return false;
  11320. }
  11321. return true;
  11322. }
  11323. // @O Straight/bezier edge approximate collision, precise collision, and distance calculation functions
  11324. {
  11325. CanvasRenderer.prototype.boxInBezierVicinity = function(
  11326. x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
  11327. // Return values:
  11328. // 0 - curve is not in box
  11329. // 1 - curve may be in box; needs precise check
  11330. // 2 - curve is in box
  11331. // midpoint
  11332. var midX = 0.25 * x1 + 0.5 * x2 + 0.25 * x3;
  11333. var midY = 0.25 * y1 + 0.5 * y2 + 0.25 * y3;
  11334. var boxMinX = Math.min(x1box, x2box) - tolerance;
  11335. var boxMinY = Math.min(y1box, y2box) - tolerance;
  11336. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  11337. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  11338. if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) { // (x1, y1) in box
  11339. return 1;
  11340. } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) { // (x3, y3) in box
  11341. return 1;
  11342. } else if (midX >= boxMinX && midX <= boxMaxX && midY >= boxMinY && midY <= boxMaxY) { // (midX, midY) in box
  11343. return 1;
  11344. } else if (x2 >= boxMinX && x2 <= boxMaxX && y2 >= boxMinY && y2 <= boxMaxY) { // ctrl pt in box
  11345. return 1;
  11346. }
  11347. var curveMinX = Math.min(x1, midX, x3);
  11348. var curveMinY = Math.min(y1, midY, y3);
  11349. var curveMaxX = Math.max(x1, midX, x3);
  11350. var curveMaxY = Math.max(y1, midY, y3);
  11351. /*
  11352. console.log(curveMinX + ", " + curveMinY + ", " + curveMaxX
  11353. + ", " + curveMaxY);
  11354. if (curveMinX == undefined) {
  11355. console.log("undefined curveMinX: " + x1 + ", " + x2 + ", " + x3);
  11356. }
  11357. */
  11358. if (curveMinX > boxMaxX
  11359. || curveMaxX < boxMinX
  11360. || curveMinY > boxMaxY
  11361. || curveMaxY < boxMinY) {
  11362. return 0;
  11363. }
  11364. return 1;
  11365. }
  11366. CanvasRenderer.prototype.checkBezierInBox = function(
  11367. x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
  11368. function qbezierAt(p0, p1, p2, t){
  11369. return (1 - t)*(1 - t)*p0 + 2*(1 - t)*t*p1 + t*t*p2;
  11370. }
  11371. function sampleInBox(t){
  11372. var x = qbezierAt(x1, x2, x3, t);
  11373. var y = qbezierAt(y1, y2, y3, t);
  11374. return x1box <= x && x <= x2box
  11375. && y1box <= y && y <= y2box
  11376. ;
  11377. }
  11378. for( var t = 0; t <= 1; t += 0.25 ){
  11379. if( !sampleInBox(t) ){
  11380. return false;
  11381. }
  11382. }
  11383. return true;
  11384. };
  11385. CanvasRenderer.prototype.checkStraightEdgeInBox = function(
  11386. x1box, y1box, x2box, y2box, x1, y1, x2, y2, tolerance) {
  11387. return x1box <= x1 && x1 <= x2box
  11388. && x1box <= x2 && x2 <= x2box
  11389. && y1box <= y1 && y1 <= y2box
  11390. && y1box <= y2 && y2 <= y2box
  11391. ;
  11392. };
  11393. CanvasRenderer.prototype.checkStraightEdgeCrossesBox = function(
  11394. x1box, y1box, x2box, y2box, x1, y1, x2, y2, tolerance) {
  11395. //console.log(arguments);
  11396. var boxMinX = Math.min(x1box, x2box) - tolerance;
  11397. var boxMinY = Math.min(y1box, y2box) - tolerance;
  11398. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  11399. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  11400. // Check left + right bounds
  11401. var aX = x2 - x1;
  11402. var bX = x1;
  11403. var yValue;
  11404. // Top and bottom
  11405. var aY = y2 - y1;
  11406. var bY = y1;
  11407. var xValue;
  11408. if (Math.abs(aX) < 0.0001) {
  11409. return (x1 >= boxMinX && x1 <= boxMaxX
  11410. && Math.min(y1, y2) <= boxMinY
  11411. && Math.max(y1, y2) >= boxMaxY);
  11412. }
  11413. var tLeft = (boxMinX - bX) / aX;
  11414. if (tLeft > 0 && tLeft <= 1) {
  11415. yValue = aY * tLeft + bY;
  11416. if (yValue >= boxMinY && yValue <= boxMaxY) {
  11417. return true;
  11418. }
  11419. }
  11420. var tRight = (boxMaxX - bX) / aX;
  11421. if (tRight > 0 && tRight <= 1) {
  11422. yValue = aY * tRight + bY;
  11423. if (yValue >= boxMinY && yValue <= boxMaxY) {
  11424. return true;
  11425. }
  11426. }
  11427. var tTop = (boxMinY - bY) / aY;
  11428. if (tTop > 0 && tTop <= 1) {
  11429. xValue = aX * tTop + bX;
  11430. if (xValue >= boxMinX && xValue <= boxMaxX) {
  11431. return true;
  11432. }
  11433. }
  11434. var tBottom = (boxMaxY - bY) / aY;
  11435. if (tBottom > 0 && tBottom <= 1) {
  11436. xValue = aX * tBottom + bX;
  11437. if (xValue >= boxMinX && xValue <= boxMaxX) {
  11438. return true;
  11439. }
  11440. }
  11441. return false;
  11442. }
  11443. CanvasRenderer.prototype.checkBezierCrossesBox = function(
  11444. x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
  11445. var boxMinX = Math.min(x1box, x2box) - tolerance;
  11446. var boxMinY = Math.min(y1box, y2box) - tolerance;
  11447. var boxMaxX = Math.max(x1box, x2box) + tolerance;
  11448. var boxMaxY = Math.max(y1box, y2box) + tolerance;
  11449. if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) {
  11450. return true;
  11451. } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) {
  11452. return true;
  11453. }
  11454. var aX = x1 - 2 * x2 + x3;
  11455. var bX = -2 * x1 + 2 * x2;
  11456. var cX = x1;
  11457. var xIntervals = [];
  11458. if (Math.abs(aX) < 0.0001) {
  11459. var leftParam = (boxMinX - x1) / bX;
  11460. var rightParam = (boxMaxX - x1) / bX;
  11461. xIntervals.push(leftParam, rightParam);
  11462. } else {
  11463. // Find when x coordinate of the curve crosses the left side of the box
  11464. var discriminantX1 = bX * bX - 4 * aX * (cX - boxMinX);
  11465. var tX1, tX2;
  11466. if (discriminantX1 > 0) {
  11467. var sqrt = Math.sqrt(discriminantX1);
  11468. tX1 = (-bX + sqrt) / (2 * aX);
  11469. tX2 = (-bX - sqrt) / (2 * aX);
  11470. xIntervals.push(tX1, tX2);
  11471. }
  11472. var discriminantX2 = bX * bX - 4 * aX * (cX - boxMaxX);
  11473. var tX3, tX4;
  11474. if (discriminantX2 > 0) {
  11475. var sqrt = Math.sqrt(discriminantX2);
  11476. tX3 = (-bX + sqrt) / (2 * aX);
  11477. tX4 = (-bX - sqrt) / (2 * aX);
  11478. xIntervals.push(tX3, tX4);
  11479. }
  11480. }
  11481. xIntervals.sort(function(a, b) { return a - b; });
  11482. var aY = y1 - 2 * y2 + y3;
  11483. var bY = -2 * y1 + 2 * y2;
  11484. var cY = y1;
  11485. var yIntervals = [];
  11486. if (Math.abs(aY) < 0.0001) {
  11487. var topParam = (boxMinY - y1) / bY;
  11488. var bottomParam = (boxMaxY - y1) / bY;
  11489. yIntervals.push(topParam, bottomParam);
  11490. } else {
  11491. var discriminantY1 = bY * bY - 4 * aY * (cY - boxMinY);
  11492. var tY1, tY2;
  11493. if (discriminantY1 > 0) {
  11494. var sqrt = Math.sqrt(discriminantY1);
  11495. tY1 = (-bY + sqrt) / (2 * aY);
  11496. tY2 = (-bY - sqrt) / (2 * aY);
  11497. yIntervals.push(tY1, tY2);
  11498. }
  11499. var discriminantY2 = bY * bY - 4 * aY * (cY - boxMaxY);
  11500. var tY3, tY4;
  11501. if (discriminantY2 > 0) {
  11502. var sqrt = Math.sqrt(discriminantY2);
  11503. tY3 = (-bY + sqrt) / (2 * aY);
  11504. tY4 = (-bY - sqrt) / (2 * aY);
  11505. yIntervals.push(tY3, tY4);
  11506. }
  11507. }
  11508. yIntervals.sort(function(a, b) { return a - b; });
  11509. for (var index = 0; index < xIntervals.length; index += 2) {
  11510. for (var yIndex = 1; yIndex < yIntervals.length; yIndex += 2) {
  11511. // Check if there exists values for the Bezier curve
  11512. // parameter between 0 and 1 where both the curve's
  11513. // x and y coordinates are within the bounds specified by the box
  11514. if (xIntervals[index] < yIntervals[yIndex]
  11515. && yIntervals[yIndex] >= 0.0
  11516. && xIntervals[index] <= 1.0
  11517. && xIntervals[index + 1] > yIntervals[yIndex - 1]
  11518. && yIntervals[yIndex - 1] <= 1.0
  11519. && xIntervals[index + 1] >= 0.0) {
  11520. return true;
  11521. }
  11522. }
  11523. }
  11524. return false;
  11525. }
  11526. CanvasRenderer.prototype.inLineVicinity = function(x, y, lx1, ly1, lx2, ly2, tolerance){
  11527. var t = tolerance;
  11528. var x1 = Math.min(lx1, lx2);
  11529. var x2 = Math.max(lx1, lx2);
  11530. var y1 = Math.min(ly1, ly2);
  11531. var y2 = Math.max(ly1, ly2);
  11532. return x1 - t <= x && x <= x2 + t
  11533. && y1 - t <= y && y <= y2 + t;
  11534. };
  11535. CanvasRenderer.prototype.inBezierVicinity = function(
  11536. x, y, x1, y1, x2, y2, x3, y3, toleranceSquared) {
  11537. // Middle point occurs when t = 0.5, this is when the Bezier
  11538. // is closest to (x2, y2)
  11539. var middlePointX = 0.25 * x1 + 0.5 * x2 + 0.25 * x3;
  11540. var middlePointY = 0.25 * y1 + 0.5 * y2 + 0.25 * y3;
  11541. // a rough bounding box of the bezier curve
  11542. var bb = {
  11543. x1: Math.min( x1, x3, middlePointX ),
  11544. x2: Math.max( x1, x3, middlePointX ),
  11545. y1: Math.min( y1, y3, middlePointY ),
  11546. y2: Math.max( y1, y3, middlePointY )
  11547. };
  11548. // if outside the rough bounding box for the bezier, then it can't be a hit
  11549. if( x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2 ){
  11550. // console.log('bezier out of rough bb')
  11551. return false;
  11552. } else {
  11553. // console.log('do more expensive check');
  11554. }
  11555. var displacementX, displacementY, offsetX, offsetY;
  11556. var dotProduct, dotSquared, hypSquared;
  11557. var outside = function(x, y, startX, startY, endX, endY,
  11558. toleranceSquared, counterClockwise) {
  11559. dotProduct = (endY - startY) * (x - startX) + (startX - endX) * (y - startY);
  11560. dotSquared = dotProduct * dotProduct;
  11561. sideSquared = (endY - startY) * (endY - startY)
  11562. + (startX - endX) * (startX - endX);
  11563. if (counterClockwise) {
  11564. if (dotProduct > 0) {
  11565. return false;
  11566. }
  11567. } else {
  11568. if (dotProduct < 0) {
  11569. return false;
  11570. }
  11571. }
  11572. return (dotSquared / sideSquared > toleranceSquared);
  11573. };
  11574. // Used to check if the test polygon winding is clockwise or counterclockwise
  11575. var testPointX = (middlePointX + x2) / 2.0;
  11576. var testPointY = (middlePointY + y2) / 2.0;
  11577. var counterClockwise = true;
  11578. // The test point is always inside
  11579. if (outside(testPointX, testPointY, x1, y1, x2, y2, 0, counterClockwise)) {
  11580. counterClockwise = !counterClockwise;
  11581. }
  11582. /*
  11583. return (!outside(x, y, x1, y1, x2, y2, toleranceSquared, counterClockwise)
  11584. && !outside(x, y, x2, y2, x3, y3, toleranceSquared, counterClockwise)
  11585. && !outside(x, y, x3, y3, middlePointX, middlePointY, toleranceSquared,
  11586. counterClockwise)
  11587. && !outside(x, y, middlePointX, middlePointY, x1, y1, toleranceSquared,
  11588. counterClockwise)
  11589. );
  11590. */
  11591. return (!outside(x, y, x1, y1, x2, y2, toleranceSquared, counterClockwise)
  11592. && !outside(x, y, x2, y2, x3, y3, toleranceSquared, counterClockwise)
  11593. && !outside(x, y, x3, y3, x1, y1, toleranceSquared,
  11594. counterClockwise)
  11595. );
  11596. }
  11597. CanvasRenderer.prototype.solveCubic = function(a, b, c, d, result) {
  11598. // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where
  11599. // r is the real component, i is the imaginary component
  11600. // An implementation of the Cardano method from the year 1545
  11601. // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots
  11602. b /= a;
  11603. c /= a;
  11604. d /= a;
  11605. var discriminant, q, r, dum1, s, t, term1, r13;
  11606. q = (3.0 * c - (b * b)) / 9.0;
  11607. r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b));
  11608. r /= 54.0;
  11609. discriminant = q * q * q + r * r;
  11610. result[1] = 0;
  11611. term1 = (b / 3.0);
  11612. if (discriminant > 0) {
  11613. s = r + Math.sqrt(discriminant);
  11614. s = ((s < 0) ? -Math.pow(-s, (1.0 / 3.0)) : Math.pow(s, (1.0 / 3.0)));
  11615. t = r - Math.sqrt(discriminant);
  11616. t = ((t < 0) ? -Math.pow(-t, (1.0 / 3.0)) : Math.pow(t, (1.0 / 3.0)));
  11617. result[0] = -term1 + s + t;
  11618. term1 += (s + t) / 2.0;
  11619. result[4] = result[2] = -term1;
  11620. term1 = Math.sqrt(3.0) * (-t + s) / 2;
  11621. result[3] = term1;
  11622. result[5] = -term1;
  11623. return;
  11624. }
  11625. result[5] = result[3] = 0;
  11626. if (discriminant == 0) {
  11627. r13 = ((r < 0) ? -Math.pow(-r, (1.0 / 3.0)) : Math.pow(r, (1.0 / 3.0)));
  11628. result[0] = -term1 + 2.0 * r13;
  11629. result[4] = result[2] = -(r13 + term1);
  11630. return;
  11631. }
  11632. q = -q;
  11633. dum1 = q * q * q;
  11634. dum1 = Math.acos(r / Math.sqrt(dum1));
  11635. r13 = 2.0 * Math.sqrt(q);
  11636. result[0] = -term1 + r13 * Math.cos(dum1 / 3.0);
  11637. result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0);
  11638. result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0);
  11639. return;
  11640. }
  11641. CanvasRenderer.prototype.sqDistanceToQuadraticBezier = function(
  11642. x, y, x1, y1, x2, y2, x3, y3) {
  11643. // Find minimum distance by using the minimum of the distance
  11644. // function between the given point and the curve
  11645. // This gives the coefficients of the resulting cubic equation
  11646. // whose roots tell us where a possible minimum is
  11647. // (Coefficients are divided by 4)
  11648. var a = 1.0 * x1*x1 - 4*x1*x2 + 2*x1*x3 + 4*x2*x2 - 4*x2*x3 + x3*x3
  11649. + y1*y1 - 4*y1*y2 + 2*y1*y3 + 4*y2*y2 - 4*y2*y3 + y3*y3;
  11650. var b = 1.0 * 9*x1*x2 - 3*x1*x1 - 3*x1*x3 - 6*x2*x2 + 3*x2*x3
  11651. + 9*y1*y2 - 3*y1*y1 - 3*y1*y3 - 6*y2*y2 + 3*y2*y3;
  11652. var c = 1.0 * 3*x1*x1 - 6*x1*x2 + x1*x3 - x1*x + 2*x2*x2 + 2*x2*x - x3*x
  11653. + 3*y1*y1 - 6*y1*y2 + y1*y3 - y1*y + 2*y2*y2 + 2*y2*y - y3*y;
  11654. var d = 1.0 * x1*x2 - x1*x1 + x1*x - x2*x
  11655. + y1*y2 - y1*y1 + y1*y - y2*y;
  11656. debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a);
  11657. var roots = [];
  11658. // Use the cubic solving algorithm
  11659. this.solveCubic(a, b, c, d, roots);
  11660. var zeroThreshold = 0.0000001;
  11661. var params = [];
  11662. for (var index = 0; index < 6; index += 2) {
  11663. if (Math.abs(roots[index + 1]) < zeroThreshold
  11664. && roots[index] >= 0
  11665. && roots[index] <= 1.0) {
  11666. params.push(roots[index]);
  11667. }
  11668. }
  11669. params.push(1.0);
  11670. params.push(0.0);
  11671. var minDistanceSquared = -1;
  11672. var closestParam;
  11673. var curX, curY, distSquared;
  11674. for (var i = 0; i < params.length; i++) {
  11675. curX = Math.pow(1.0 - params[i], 2.0) * x1
  11676. + 2.0 * (1 - params[i]) * params[i] * x2
  11677. + params[i] * params[i] * x3;
  11678. curY = Math.pow(1 - params[i], 2.0) * y1
  11679. + 2 * (1.0 - params[i]) * params[i] * y2
  11680. + params[i] * params[i] * y3;
  11681. distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2);
  11682. debug("distance for param " + params[i] + ": " + Math.sqrt(distSquared));
  11683. if (minDistanceSquared >= 0) {
  11684. if (distSquared < minDistanceSquared) {
  11685. minDistanceSquared = distSquared;
  11686. closestParam = params[i];
  11687. }
  11688. } else {
  11689. minDistanceSquared = distSquared;
  11690. closestParam = params[i];
  11691. }
  11692. }
  11693. /*
  11694. debugStats.clickX = x;
  11695. debugStats.clickY = y;
  11696. debugStats.closestX = Math.pow(1.0 - closestParam, 2.0) * x1
  11697. + 2.0 * (1.0 - closestParam) * closestParam * x2
  11698. + closestParam * closestParam * x3;
  11699. debugStats.closestY = Math.pow(1.0 - closestParam, 2.0) * y1
  11700. + 2.0 * (1.0 - closestParam) * closestParam * y2
  11701. + closestParam * closestParam * y3;
  11702. */
  11703. debug("given: "
  11704. + "( " + x + ", " + y + "), "
  11705. + "( " + x1 + ", " + y1 + "), "
  11706. + "( " + x2 + ", " + y2 + "), "
  11707. + "( " + x3 + ", " + y3 + ")");
  11708. debug("roots: " + roots);
  11709. debug("params: " + params);
  11710. debug("closest param: " + closestParam);
  11711. return minDistanceSquared;
  11712. }
  11713. CanvasRenderer.prototype.sqDistanceToFiniteLine = function(x, y, x1, y1, x2, y2) {
  11714. var offset = [x - x1, y - y1];
  11715. var line = [x2 - x1, y2 - y1];
  11716. var lineSq = line[0] * line[0] + line[1] * line[1];
  11717. var hypSq = offset[0] * offset[0] + offset[1] * offset[1];
  11718. var dotProduct = offset[0] * line[0] + offset[1] * line[1];
  11719. var adjSq = dotProduct * dotProduct / lineSq;
  11720. if (dotProduct < 0) {
  11721. return hypSq;
  11722. }
  11723. if (adjSq > lineSq) {
  11724. return (x - x2) * (x - x2) + (y - y2) * (y - y2);
  11725. }
  11726. return (hypSq - adjSq)
  11727. }
  11728. }
  11729. }
  11730. var debug = function(){};
  11731. $$("renderer", "canvas", CanvasRenderer);
  11732. })( cytoscape );
  11733. ;(function($$){
  11734. // default layout options
  11735. var defaults = {
  11736. ready: function(){},
  11737. stop: function(){}
  11738. };
  11739. // constructor
  11740. // options : object containing layout options
  11741. function NullLayout( options ){
  11742. this.options = $$.util.extend(true, {}, defaults, options);
  11743. }
  11744. // runs the layout
  11745. NullLayout.prototype.run = function(){
  11746. var options = this.options;
  11747. var cy = options.cy; // cy is automatically populated for us in the constructor
  11748. // puts all nodes at (0, 0)
  11749. cy.nodes().positions(function(){
  11750. return {
  11751. x: 0,
  11752. y: 0
  11753. };
  11754. });
  11755. // trigger layoutready when each node has had its position set at least once
  11756. cy.one("layoutready", options.ready);
  11757. cy.trigger("layoutready");
  11758. // trigger layoutstop when the layout stops (e.g. finishes)
  11759. cy.one("layoutstop", options.stop);
  11760. cy.trigger("layoutstop");
  11761. };
  11762. // called on continuous layouts to stop them before they finish
  11763. NullLayout.prototype.stop = function(){
  11764. var options = this.options;
  11765. cy.one("layoutstop", options.stop);
  11766. cy.trigger("layoutstop");
  11767. };
  11768. // register the layout
  11769. $$("layout", "null", NullLayout);
  11770. })(cytoscape);
  11771. ;(function($$){
  11772. var defaults = {
  11773. ready: undefined, // callback on layoutready
  11774. stop: undefined, // callback on layoutstop
  11775. fit: true, // whether to fit to viewport
  11776. padding: 30 // fit padding
  11777. };
  11778. function RandomLayout( options ){
  11779. this.options = $$.util.extend(true, {}, defaults, options);
  11780. }
  11781. RandomLayout.prototype.run = function(){
  11782. var options = this.options;
  11783. var cy = options.cy;
  11784. var nodes = cy.nodes();
  11785. var edges = cy.edges();
  11786. var container = cy.container();
  11787. var width = container.clientWidth;
  11788. var height = container.clientHeight;
  11789. nodes.positions(function(i, element){
  11790. if( element.locked() ){
  11791. return false;
  11792. }
  11793. return {
  11794. x: Math.round( Math.random() * width ),
  11795. y: Math.round( Math.random() * height )
  11796. };
  11797. });
  11798. // layoutready should be triggered when the layout has set each node's
  11799. // position at least once
  11800. cy.one("layoutready", options.ready);
  11801. cy.trigger("layoutready");
  11802. if( options.fit ){
  11803. cy.fit( options.padding );
  11804. }
  11805. // layoutstop should be triggered when the layout stops running
  11806. cy.one("layoutstop", options.stop);
  11807. cy.trigger("layoutstop");
  11808. };
  11809. RandomLayout.prototype.stop = function(){
  11810. // stop the layout if it were running continuously
  11811. };
  11812. // register the layout
  11813. $$(
  11814. "layout", // we're registering a layout
  11815. "random", // the layout name
  11816. RandomLayout // the layout prototype
  11817. );
  11818. })(cytoscape);
  11819. ;(function($$){
  11820. var defaults = {
  11821. fit: true, // whether to fit the viewport to the graph
  11822. rows: undefined, // force num of rows in the grid
  11823. columns: undefined, // force num of cols in the grid
  11824. ready: undefined, // callback on layoutready
  11825. stop: undefined // callback on layoutstop
  11826. };
  11827. function GridLayout( options ){
  11828. this.options = $$.util.extend({}, defaults, options);
  11829. }
  11830. GridLayout.prototype.run = function(){
  11831. var params = this.options;
  11832. var options = params;
  11833. var cy = params.cy;
  11834. var nodes = cy.nodes();
  11835. var edges = cy.edges();
  11836. var container = cy.container();
  11837. var width = container.clientWidth;
  11838. var height = container.clientHeight;
  11839. if( height == 0 || width == 0){
  11840. nodes.positions(function(){
  11841. return { x: 0, y: 0 };
  11842. });
  11843. } else {
  11844. // width/height * splits^2 = cells where splits is number of times to split width
  11845. var cells = nodes.size();
  11846. var splits = Math.sqrt( cells * height/width );
  11847. var rows = Math.round( splits );
  11848. var cols = Math.round( width/height * splits );
  11849. function small(val){
  11850. if( val == undefined ){
  11851. return Math.min(rows, cols);
  11852. } else {
  11853. var min = Math.min(rows, cols);
  11854. if( min == rows ){
  11855. rows = val;
  11856. } else {
  11857. cols = val;
  11858. }
  11859. }
  11860. }
  11861. function large(val){
  11862. if( val == undefined ){
  11863. return Math.max(rows, cols);
  11864. } else {
  11865. var max = Math.max(rows, cols);
  11866. if( max == rows ){
  11867. rows = val;
  11868. } else {
  11869. cols = val;
  11870. }
  11871. }
  11872. }
  11873. // if rows or columns were set in options, use those values
  11874. if( options.rows != null && options.columns != null ){
  11875. rows = options.rows;
  11876. cols = options.columns;
  11877. } else if( options.rows != null && options.columns == null ){
  11878. rows = options.rows;
  11879. cols = Math.ceil( cells / rows );
  11880. } else if( options.rows == null && options.columns != null ){
  11881. cols = options.columns;
  11882. rows = Math.ceil( cells / cols );
  11883. }
  11884. // otherwise use the automatic values and adjust accordingly
  11885. // if rounding was up, see if we can reduce rows or columns
  11886. else if( cols * rows > cells ){
  11887. var sm = small();
  11888. var lg = large();
  11889. // reducing the small side takes away the most cells, so try it first
  11890. if( (sm - 1) * lg >= cells ){
  11891. small(sm - 1);
  11892. } else if( (lg - 1) * sm >= cells ){
  11893. large(lg - 1);
  11894. }
  11895. } else {
  11896. // if rounding was too low, add rows or columns
  11897. while( cols * rows < cells ){
  11898. var sm = small();
  11899. var lg = large();
  11900. // try to add to larger side first (adds less in multiplication)
  11901. if( (lg + 1) * sm >= cells ){
  11902. large(lg + 1);
  11903. } else {
  11904. small(sm + 1);
  11905. }
  11906. }
  11907. }
  11908. var cellWidth = width / cols;
  11909. var cellHeight = height / rows;
  11910. var row = 0;
  11911. var col = 0;
  11912. nodes.positions(function(i, element){
  11913. if( element.locked() ){
  11914. return false;
  11915. }
  11916. var x = col * cellWidth + cellWidth/2;
  11917. var y = row * cellHeight + cellHeight/2;
  11918. col++;
  11919. if( col >= cols ){
  11920. col = 0;
  11921. row++;
  11922. }
  11923. return { x: x, y: y };
  11924. });
  11925. }
  11926. if( params.fit ){
  11927. cy.reset();
  11928. }
  11929. cy.one("layoutready", params.ready);
  11930. cy.trigger("layoutready");
  11931. cy.one("layoutstop", params.stop);
  11932. cy.trigger("layoutstop");
  11933. };
  11934. GridLayout.prototype.stop = function(){
  11935. // not a continuous layout
  11936. };
  11937. $$("layout", "grid", GridLayout);
  11938. })( cytoscape );
  11939. ;(function($$){
  11940. var defaults = {
  11941. fit: true, // whether to fit to viewport
  11942. ready: undefined, // callback on layoutready
  11943. stop: undefined, // callback on layoutstop
  11944. positions: undefined, // map of (node id) => (position obj)
  11945. zoom: undefined, // the zoom level to set (prob want fit = false if set)
  11946. pan: undefined, // the pan level to set (prob want fit = false if set)
  11947. padding: 30 // padding on fit
  11948. };
  11949. function PresetLayout( options ){
  11950. this.options = $$.util.extend(true, {}, defaults, options);
  11951. }
  11952. PresetLayout.prototype.run = function(){
  11953. var options = this.options;
  11954. var cy = options.cy;
  11955. var nodes = cy.nodes();
  11956. var edges = cy.edges();
  11957. var container = cy.container();
  11958. function getPosition(node){
  11959. if( options.positions == null ){
  11960. return null;
  11961. }
  11962. if( options.positions[node._private.data.id] == null ){
  11963. return null;
  11964. }
  11965. return options.positions[node._private.data.id];
  11966. }
  11967. nodes.positions(function(i, node){
  11968. var position = getPosition(node);
  11969. if( node.locked() || position == null ){
  11970. return false;
  11971. }
  11972. return position;
  11973. });
  11974. if( options.pan != null ){
  11975. cy.pan( options.pan );
  11976. }
  11977. if( options.zoom != null ){
  11978. cy.zoom( options.zoom );
  11979. }
  11980. cy.one("layoutready", options.ready);
  11981. cy.trigger("layoutready");
  11982. if( options.fit ){
  11983. cy.fit( options.padding );
  11984. }
  11985. cy.one("layoutstop", options.stop);
  11986. cy.trigger("layoutstop");
  11987. };
  11988. $$("layout", "preset", PresetLayout);
  11989. $$("core", "presetLayout", function(){
  11990. var cy = this;
  11991. var layout = {};
  11992. var elements = {};
  11993. cy.nodes().each(function(i, ele){
  11994. elements[ ele.data("id") ] = ele.position();
  11995. });
  11996. layout.positions = elements;
  11997. layout.name = "preset";
  11998. layout.zoom = cy.zoom();
  11999. layout.pan = cy.pan();
  12000. return layout;
  12001. });
  12002. })(cytoscape);
  12003. ;(function($$){
  12004. var defaults = {
  12005. liveUpdate: true, // whether to show the layout as it's running
  12006. ready: undefined, // callback on layoutready
  12007. stop: undefined, // callback on layoutstop
  12008. maxSimulationTime: 4000, // max length in ms to run the layout
  12009. fit: true, // fit to viewport
  12010. padding: [ 50, 50, 50, 50 ], // top, right, bottom, left
  12011. ungrabifyWhileSimulating: true, // so you can't drag nodes during layout
  12012. // forces used by arbor (use arbor default on undefined)
  12013. repulsion: undefined,
  12014. stiffness: undefined,
  12015. friction: undefined,
  12016. gravity: true,
  12017. fps: undefined,
  12018. precision: undefined,
  12019. // static numbers or functions that dynamically return what these
  12020. // values should be for each element
  12021. nodeMass: undefined,
  12022. edgeLength: undefined,
  12023. stepSize: 1, // size of timestep in simulation
  12024. // function that returns true if the system is stable to indicate
  12025. // that the layout can be stopped
  12026. stableEnergy: function( energy ){
  12027. var e = energy;
  12028. return (e.max <= 0.5) || (e.mean <= 0.3);
  12029. }
  12030. };
  12031. function ArborLayout(options){
  12032. this.options = $$.util.extend({}, defaults, options);
  12033. }
  12034. ArborLayout.prototype.run = function(){
  12035. var options = this.options;
  12036. var cy = options.cy;
  12037. var nodes = cy.nodes();
  12038. var edges = cy.edges();
  12039. var container = cy.container();
  12040. var width = container.clientWidth;
  12041. var height = container.clientHeight;
  12042. // arbor doesn't work with just 1 node
  12043. if( cy.nodes().size() <= 1 ){
  12044. if( options.fit ){
  12045. cy.reset();
  12046. }
  12047. cy.nodes().position({
  12048. x: Math.round( width/2 ),
  12049. y: Math.round( height/2 )
  12050. });
  12051. cy.one("layoutstop", options.stop);
  12052. cy.trigger("layoutstop");
  12053. cy.one("layoutstop", options.stop);
  12054. cy.trigger("layoutstop");
  12055. return;
  12056. }
  12057. var sys = this.system = arbor.ParticleSystem(options.repulsion, options.stiffness, options.friction, options.gravity, options.fps, options.dt, options.precision);
  12058. this.system = sys;
  12059. if( options.liveUpdate && options.fit ){
  12060. cy.reset();
  12061. };
  12062. var doneTime = 250;
  12063. var doneTimeout;
  12064. var ready = false;
  12065. var lastDraw = +new Date;
  12066. var sysRenderer = {
  12067. init: function(system){
  12068. },
  12069. redraw: function(){
  12070. var energy = sys.energy();
  12071. // if we're stable (according to the client), we're done
  12072. if( options.stableEnergy != null && energy != null && energy.n > 0 && options.stableEnergy(energy) ){
  12073. sys.stop();
  12074. return;
  12075. }
  12076. clearTimeout(doneTimeout);
  12077. doneTimeout = setTimeout(doneHandler, doneTime);
  12078. var movedNodes = [];
  12079. sys.eachNode(function(n, point){
  12080. var id = n.name;
  12081. var data = n.data;
  12082. var node = data.element;
  12083. if( node == null ){
  12084. return;
  12085. }
  12086. var pos = node._private.position;
  12087. if( !node.locked() && !node.grabbed() ){
  12088. pos.x = point.x;
  12089. pos.y = point.y;
  12090. movedNodes.push( node );
  12091. }
  12092. });
  12093. var timeToDraw = (+new Date - lastDraw) >= 16;
  12094. if( options.liveUpdate && movedNodes.length > 0 && timeToDraw ){
  12095. new $$.Collection(cy, movedNodes).rtrigger("position");
  12096. lastDraw = +new Date;
  12097. }
  12098. if( !ready ){
  12099. ready = true;
  12100. cy.one("layoutready", options.ready);
  12101. cy.trigger("layoutready");
  12102. }
  12103. }
  12104. };
  12105. sys.renderer = sysRenderer;
  12106. sys.screenSize( width, height );
  12107. sys.screenPadding( options.padding[0], options.padding[1], options.padding[2], options.padding[3] );
  12108. sys.screenStep( options.stepSize );
  12109. function calculateValueForElement(element, value){
  12110. if( value == null ){
  12111. return undefined;
  12112. } else if( typeof value == typeof function(){} ){
  12113. return value.apply(element, [element._private.data, {
  12114. nodes: nodes.length,
  12115. edges: edges.length,
  12116. element: element
  12117. }]);
  12118. } else {
  12119. return value;
  12120. }
  12121. }
  12122. // TODO we're using a hack; sys.toScreen should work :(
  12123. function fromScreen(pos){
  12124. var x = pos.x;
  12125. var y = pos.y;
  12126. var w = width;
  12127. var h = height;
  12128. var left = -2;
  12129. var right = 2;
  12130. var top = -2;
  12131. var bottom = 2;
  12132. var d = 4;
  12133. return {
  12134. x: x/w * d + left,
  12135. y: y/h * d + right
  12136. };
  12137. }
  12138. var grabHandler = function(e){
  12139. grabbed = this;
  12140. var pos = sys.fromScreen( this.position() );
  12141. var p = arbor.Point(pos.x, pos.y);
  12142. this.scratch().arbor.p = p;
  12143. switch( e.type ){
  12144. case "grab":
  12145. this.scratch().arbor.fixed = true;
  12146. break;
  12147. case "dragstop":
  12148. this.scratch().arbor.fixed = false;
  12149. this.scratch().arbor.tempMass = 1000
  12150. break;
  12151. }
  12152. };
  12153. nodes.bind("grab drag dragstop", grabHandler);
  12154. nodes.each(function(i, node){
  12155. var id = this._private.data.id;
  12156. var mass = calculateValueForElement(this, options.nodeMass);
  12157. var locked = this._private.locked;
  12158. var pos = fromScreen({
  12159. x: node.position().x,
  12160. y: node.position().y
  12161. });
  12162. if( node.locked() ){
  12163. return;
  12164. }
  12165. this.scratch().arbor = sys.addNode(id, {
  12166. element: this,
  12167. mass: mass,
  12168. fixed: locked,
  12169. x: locked ? pos.x : undefined,
  12170. y: locked ? pos.y : undefined
  12171. });
  12172. });
  12173. edges.each(function(){
  12174. var id = this.id();
  12175. var src = this.source().id();
  12176. var tgt = this.target().id();
  12177. var length = calculateValueForElement(this, options.edgeLength);
  12178. this.scratch().arbor = sys.addEdge(src, tgt, {
  12179. length: length
  12180. });
  12181. });
  12182. function packToCenter(callback){
  12183. // TODO implement this for IE :(
  12184. if( options.fit ){
  12185. cy.fit();
  12186. }
  12187. callback();
  12188. };
  12189. var grabbableNodes = nodes.filter(":grabbable");
  12190. // disable grabbing if so set
  12191. if( options.ungrabifyWhileSimulating ){
  12192. grabbableNodes.ungrabify();
  12193. }
  12194. var doneHandler = function(){
  12195. if( window.isIE ){
  12196. packToCenter(function(){
  12197. done();
  12198. });
  12199. } else {
  12200. done();
  12201. }
  12202. function done(){
  12203. if( !options.liveUpdate ){
  12204. if( options.fit ){
  12205. cy.reset();
  12206. }
  12207. cy.nodes().rtrigger("position");
  12208. }
  12209. // unbind handlers
  12210. nodes.unbind("grab drag dragstop", grabHandler);
  12211. // enable back grabbing if so set
  12212. if( options.ungrabifyWhileSimulating ){
  12213. grabbableNodes.grabify();
  12214. }
  12215. cy.one("layoutstop", options.stop);
  12216. cy.trigger("layoutstop");
  12217. }
  12218. };
  12219. sys.start();
  12220. setTimeout(function(){
  12221. sys.stop();
  12222. }, options.maxSimulationTime);
  12223. };
  12224. ArborLayout.prototype.stop = function(){
  12225. if( this.system != null ){
  12226. system.stop();
  12227. }
  12228. };
  12229. $$("layout", "arbor", ArborLayout);
  12230. })(cytoscape);
  12231. ;(function($$){
  12232. var defaults = {
  12233. fit: true, // whether to fit the viewport to the graph
  12234. ready: undefined, // callback on layoutready
  12235. stop: undefined, // callback on layoutstop
  12236. rStepSize: 10, // the step size for increasing the radius if the nodes don't fit on screen
  12237. padding: 30, // the padding on fit
  12238. startAngle: 3/2 * Math.PI, // the position of the first node
  12239. counterclockwise: false // whether the layout should go counterclockwise (true) or clockwise (false)
  12240. };
  12241. function CircleLayout( options ){
  12242. this.options = $$.util.extend({}, defaults, options);
  12243. }
  12244. CircleLayout.prototype.run = function(){
  12245. var params = this.options;
  12246. var options = params;
  12247. var cy = params.cy;
  12248. var nodes = cy.nodes();
  12249. var edges = cy.edges();
  12250. var container = cy.container();
  12251. var width = container.clientWidth;
  12252. var height = container.clientHeight;
  12253. var center = {
  12254. x: width/2,
  12255. y: height/2
  12256. };
  12257. var padding = 50;
  12258. var theta = options.startAngle;
  12259. var dTheta = 2 * Math.PI / nodes.length;
  12260. var maxNodeSize = 0;
  12261. for( var i = 0; i < nodes.length; i++ ){
  12262. var node = nodes[i];
  12263. maxNodeSize = Math.max( node.outerWidth(), node.outerHeight() );
  12264. }
  12265. var r = width/2 - maxNodeSize;
  12266. function distanceBetweenNodes(){
  12267. var t1 = 0;
  12268. var t2 = dTheta;
  12269. var p1 = {
  12270. x: center.x + r * Math.cos(t1),
  12271. y: center.y + r * Math.sin(t1)
  12272. };
  12273. var p2 = {
  12274. x: center.x + r * Math.cos(t2),
  12275. y: center.y + r * Math.sin(t2)
  12276. };
  12277. var dist = Math.sqrt( (p2.x - p1.x)*(p2.x - p1.x) + (p2.y - p1.y)*(p2.y - p1.y) );
  12278. return dist;
  12279. }
  12280. while( distanceBetweenNodes() < maxNodeSize ){
  12281. r += options.rStepSize;
  12282. }
  12283. var i = 0;
  12284. nodes.positions(function(){
  12285. var node = this;
  12286. var rx = r * Math.cos( theta );
  12287. var ry = r * Math.sin( theta );
  12288. var pos = {
  12289. x: center.x + rx,
  12290. y: center.y + ry
  12291. };
  12292. i++;
  12293. theta = options.counterclockwise ? theta - dTheta : theta + dTheta;
  12294. return pos;
  12295. });
  12296. if( params.fit ){
  12297. cy.fit( options.padding );
  12298. }
  12299. cy.one("layoutready", params.ready);
  12300. cy.trigger("layoutready");
  12301. cy.one("layoutstop", params.stop);
  12302. cy.trigger("layoutstop");
  12303. };
  12304. CircleLayout.prototype.stop = function(){
  12305. // not a continuous layout
  12306. };
  12307. $$("layout", "circle", CircleLayout);
  12308. })( cytoscape );
  12309. ;(function($$){
  12310. var defaults = {
  12311. fit: true, // whether to fit the viewport to the graph
  12312. ready: undefined, // callback on layoutready
  12313. stop: undefined, // callback on layoutstop
  12314. directed: true, // whether the tree is directed downwards (or edges can point in any direction if false)
  12315. padding: 30, // padding on fit
  12316. circle: false, // put depths in concentric circles if true, put depths top down if false
  12317. roots: undefined // the roots of the trees
  12318. };
  12319. function BreadthFirstLayout( options ){
  12320. this.options = $$.util.extend({}, defaults, options);
  12321. }
  12322. BreadthFirstLayout.prototype.run = function(){
  12323. var params = this.options;
  12324. var options = params;
  12325. var cy = params.cy;
  12326. var nodes = cy.nodes();
  12327. var edges = cy.edges();
  12328. var container = cy.container();
  12329. var width = container.clientWidth;
  12330. var height = container.clientHeight;
  12331. var roots;
  12332. if( $$.is.elementOrCollection(options.roots) ){
  12333. roots = options.roots;
  12334. } else if( $$.is.array(options.roots) ){
  12335. var rootsArray = [];
  12336. for( var i = 0; i < options.roots.length; i++ ){
  12337. var id = options.roots[i];
  12338. var ele = cy.getElementById( id );
  12339. roots.push( ele );
  12340. }
  12341. roots = new $$.Collection( cy, rootsArray );
  12342. } else {
  12343. roots = nodes.roots();
  12344. }
  12345. var depths = [];
  12346. var foundByBfs = {};
  12347. var id2depth = {};
  12348. // find the depths of the nodes
  12349. roots.bfs(function(i, depth){
  12350. var ele = this[0];
  12351. if( !depths[depth] ){
  12352. depths[depth] = [];
  12353. }
  12354. depths[depth].push( ele );
  12355. foundByBfs[ ele.id() ] = true;
  12356. id2depth[ ele.id() ] = depth;
  12357. }, options.directed);
  12358. // check for nodes not found by bfs
  12359. var orphanNodes = [];
  12360. for( var i = 0; i < nodes.length; i++ ){
  12361. var ele = nodes[i];
  12362. if( foundByBfs[ ele.id() ] ){
  12363. continue;
  12364. } else {
  12365. orphanNodes.push( ele );
  12366. }
  12367. }
  12368. // assign orphan nodes a depth from their neighborhood
  12369. var maxChecks = orphanNodes.length * 3;
  12370. var checks = 0;
  12371. while( orphanNodes.length !== 0 && checks < maxChecks ){
  12372. var node = orphanNodes.shift();
  12373. var neighbors = node.neighborhood().nodes();
  12374. var assignedDepth = false;
  12375. for( var i = 0; i < neighbors.length; i++ ){
  12376. var depth = id2depth[ neighbors[i].id() ];
  12377. if( depth !== undefined ){
  12378. depths[depth].push( node );
  12379. assignedDepth = true;
  12380. break;
  12381. }
  12382. }
  12383. if( !assignedDepth ){
  12384. orphanNodes.push( node );
  12385. }
  12386. checks++;
  12387. }
  12388. // assign orphan nodes that are still left to the depth of their subgraph
  12389. while( orphanNodes.length !== 0 ){
  12390. var node = orphanNodes.shift();
  12391. var subgraph = node.bfs();
  12392. var assignedDepth = false;
  12393. for( var i = 0; i < subgraph.length; i++ ){
  12394. var depth = id2depth[ subgraph[i].id() ];
  12395. if( depth !== undefined ){
  12396. depths[depth].push( node );
  12397. assignedDepth = true;
  12398. break;
  12399. }
  12400. }
  12401. if( !assignedDepth ){ // worst case if the graph really isn't tree friendly, then just dump it in 0
  12402. if( depths.length === 0 ){
  12403. depths.push([]);
  12404. }
  12405. depths[0].push( node );
  12406. }
  12407. }
  12408. // assign the nodes a depth and index
  12409. function assignDepthsToEles(){
  12410. for( var i = 0; i < depths.length; i++ ){
  12411. var eles = depths[i];
  12412. for( var j = 0; j < eles.length; j++ ){
  12413. var ele = eles[j];
  12414. ele._private.scratch.BreadthFirstLayout = {
  12415. depth: i,
  12416. index: j
  12417. };
  12418. }
  12419. }
  12420. }
  12421. assignDepthsToEles();
  12422. // find min distance we need to leave between nodes
  12423. var minDistance = 0;
  12424. for( var i = 0; i < nodes.length; i++ ){
  12425. var w = nodes[i].outerWidth();
  12426. var h = nodes[i].outerHeight();
  12427. minDistance = Math.max(minDistance, w, h);
  12428. }
  12429. minDistance *= 1.75; // just to have some nice spacing
  12430. // get the weighted percent for an element based on its connectivity to other levels
  12431. var cachedWeightedPercent = {};
  12432. function getWeightedPercent( ele ){
  12433. if( cachedWeightedPercent[ ele.id() ] ){
  12434. return cachedWeightedPercent[ ele.id() ];
  12435. }
  12436. var eleDepth = ele._private.scratch.BreadthFirstLayout.depth;
  12437. var neighbors = ele.neighborhood().nodes();
  12438. var percent = 0;
  12439. var samples = 0;
  12440. for( var i = 0; i < neighbors.length; i++ ){
  12441. var neighbor = neighbors[i];
  12442. var nEdges = neighbor.edgesWith( ele );
  12443. var index = neighbor._private.scratch.BreadthFirstLayout.index;
  12444. var depth = neighbor._private.scratch.BreadthFirstLayout.depth;
  12445. var nDepth = depths[depth].length;
  12446. if( eleDepth > depth || eleDepth === 0 ){ // only get influenced by elements above
  12447. percent += index / nDepth;
  12448. samples++;
  12449. }
  12450. }
  12451. samples = Math.max(1, samples);
  12452. percent = percent / samples;
  12453. if( samples === 0 ){ // so lone nodes have a "don't care" state in sorting
  12454. percent = undefined;
  12455. }
  12456. cachedWeightedPercent[ ele.id() ] = percent;
  12457. return percent;
  12458. }
  12459. // rearrange the indices in each depth level based on connectivity
  12460. for( var times = 0; times < 3; times++ ){ // do it a few times b/c the depths are dynamic and we want a more stable result
  12461. for( var i = 0; i < depths.length; i++ ){
  12462. var depth = i;
  12463. var newDepths = [];
  12464. depths[i] = depths[i].sort(function(a, b){
  12465. var apct = getWeightedPercent( a );
  12466. var bpct = getWeightedPercent( b );
  12467. return apct - bpct;
  12468. });
  12469. }
  12470. assignDepthsToEles(); // and update
  12471. }
  12472. var center = {
  12473. x: width/2,
  12474. y: height/2
  12475. };
  12476. nodes.positions(function(){
  12477. var ele = this[0];
  12478. var info = ele._private.scratch.BreadthFirstLayout;
  12479. var depth = info.depth;
  12480. var index = info.index;
  12481. var distanceX = Math.max( width / (depths[depth].length + 1), minDistance );
  12482. var distanceY = Math.max( height / (depths.length + 1), minDistance );
  12483. var radiusStepSize = Math.min( width / 2 / depths.length, height / 2 / depths.length );
  12484. radiusStepSize = Math.max( radiusStepSize, minDistance );
  12485. if( options.circle ){
  12486. var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize/2 : 0);
  12487. var theta = 2 * Math.PI / depths[depth].length * index;
  12488. if( depth === 0 && depths[0].length === 1 ){
  12489. radius = 1;
  12490. }
  12491. return {
  12492. x: center.x + radius * Math.cos(theta),
  12493. y: center.y + radius * Math.sin(theta)
  12494. };
  12495. } else {
  12496. return {
  12497. x: (index + 1) * distanceX,
  12498. y: (depth + 1) * distanceY
  12499. };
  12500. }
  12501. });
  12502. if( params.fit ){
  12503. cy.fit( options.padding );
  12504. }
  12505. cy.one("layoutready", params.ready);
  12506. cy.trigger("layoutready");
  12507. cy.one("layoutstop", params.stop);
  12508. cy.trigger("layoutstop");
  12509. };
  12510. BreadthFirstLayout.prototype.stop = function(){
  12511. // not a continuous layout
  12512. };
  12513. $$("layout", "breadthfirst", BreadthFirstLayout);
  12514. })( cytoscape );